├── .coveragerc ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── docs ├── Makefile ├── _static │ ├── favicon.ico │ └── google_logo.png ├── conf.py ├── index.rst ├── requirements.txt └── source │ ├── oauth2client.client.rst │ ├── oauth2client.clientsecrets.rst │ ├── oauth2client.contrib.appengine.rst │ ├── oauth2client.contrib.devshell.rst │ ├── oauth2client.contrib.dictionary_storage.rst │ ├── oauth2client.contrib.django_util.apps.rst │ ├── oauth2client.contrib.django_util.decorators.rst │ ├── oauth2client.contrib.django_util.models.rst │ ├── oauth2client.contrib.django_util.rst │ ├── oauth2client.contrib.django_util.signals.rst │ ├── oauth2client.contrib.django_util.site.rst │ ├── oauth2client.contrib.django_util.storage.rst │ ├── oauth2client.contrib.django_util.views.rst │ ├── oauth2client.contrib.flask_util.rst │ ├── oauth2client.contrib.gce.rst │ ├── oauth2client.contrib.keyring_storage.rst │ ├── oauth2client.contrib.multiprocess_file_storage.rst │ ├── oauth2client.contrib.rst │ ├── oauth2client.contrib.sqlalchemy.rst │ ├── oauth2client.contrib.xsrfutil.rst │ ├── oauth2client.crypt.rst │ ├── oauth2client.file.rst │ ├── oauth2client.rst │ ├── oauth2client.service_account.rst │ ├── oauth2client.tools.rst │ └── oauth2client.transport.rst ├── oauth2client ├── __init__.py ├── _helpers.py ├── _openssl_crypt.py ├── _pkce.py ├── _pure_python_crypt.py ├── _pycrypto_crypt.py ├── client.py ├── clientsecrets.py ├── contrib │ ├── __init__.py │ ├── _appengine_ndb.py │ ├── _metadata.py │ ├── appengine.py │ ├── devshell.py │ ├── dictionary_storage.py │ ├── django_util │ │ ├── __init__.py │ │ ├── apps.py │ │ ├── decorators.py │ │ ├── models.py │ │ ├── signals.py │ │ ├── site.py │ │ ├── storage.py │ │ └── views.py │ ├── flask_util.py │ ├── gce.py │ ├── keyring_storage.py │ ├── multiprocess_file_storage.py │ ├── sqlalchemy.py │ └── xsrfutil.py ├── crypt.py ├── file.py ├── service_account.py ├── tools.py └── transport.py ├── samples ├── call_compute_service.py ├── django │ ├── README.md │ ├── django_user │ │ ├── manage.py │ │ ├── myoauth │ │ │ ├── __init__.py │ │ │ ├── settings.py │ │ │ ├── urls.py │ │ │ └── wsgi.py │ │ ├── polls │ │ │ ├── __init__.py │ │ │ ├── models.py │ │ │ ├── templates │ │ │ │ └── registration │ │ │ │ │ └── login.html │ │ │ └── views.py │ │ └── requirements.txt │ └── google_user │ │ ├── manage.py │ │ ├── myoauth │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ │ ├── polls │ │ ├── __init__.py │ │ └── views.py │ │ └── requirements.txt ├── googleappengine │ ├── app.yaml │ └── call_compute_service_from_gae.py └── oauth2_for_devices.py ├── scripts ├── build_docs.sh ├── install.sh ├── local_test_setup.sample ├── run.sh ├── run_gce_system_tests.py ├── run_system_tests.py └── run_system_tests.sh ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── conftest.py ├── contrib │ ├── __init__.py │ ├── appengine │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test__appengine_ndb.py │ │ └── test_appengine.py │ ├── django_util │ │ ├── __init__.py │ │ ├── apps.py │ │ ├── models.py │ │ ├── settings.py │ │ ├── test_decorators.py │ │ ├── test_django_models.py │ │ ├── test_django_storage.py │ │ ├── test_django_util.py │ │ └── test_views.py │ ├── test_devshell.py │ ├── test_dictionary_storage.py │ ├── test_flask_util.py │ ├── test_gce.py │ ├── test_keyring_storage.py │ ├── test_metadata.py │ ├── test_multiprocess_file_storage.py │ ├── test_sqlalchemy.py │ └── test_xsrfutil.py ├── data │ ├── app.yaml │ ├── certs.json │ ├── client_secrets.json │ ├── gcloud │ │ ├── application_default_credentials.json │ │ ├── application_default_credentials_authorized_user.json │ │ ├── application_default_credentials_malformed_1.json │ │ ├── application_default_credentials_malformed_2.json │ │ └── application_default_credentials_malformed_3.json │ ├── key.json.enc │ ├── key.p12.enc │ ├── pem_from_pkcs12.pem │ ├── pem_from_pkcs12_alternate.pem │ ├── privatekey.p12 │ ├── privatekey.pem │ ├── privatekey.pub │ ├── public_cert.pem │ ├── publickey_openssl.pem │ ├── unfilled_client_secrets.json │ └── user-key.json.enc ├── http_mock.py ├── test__helpers.py ├── test__pkce.py ├── test__pure_python_crypt.py ├── test__pycrypto_crypt.py ├── test_client.py ├── test_clientsecrets.py ├── test_crypt.py ├── test_file.py ├── test_jwt.py ├── test_service_account.py ├── test_tools.py └── test_transport.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | 4 | [report] 5 | omit = 6 | */samples/* 7 | */conftest.py 8 | # Don't report coverage over platform-specific modules. 9 | oauth2client/contrib/_fcntl_opener.py 10 | oauth2client/contrib/_win32_opener.py 11 | oauth2client/contrib/django_util/apps.py 12 | exclude_lines = 13 | # Re-enable the standard pragma 14 | pragma: NO COVER 15 | # Ignore debug-only repr 16 | def __repr__ 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Note**: oauth2client is now deprecated. As such, it is unlikely that we will 2 | address or respond to your issue. We recommend you use 3 | [google-auth](https://google-auth.readthedocs.io) and [oauthlib](http://oauthlib.readthedocs.io/). 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Note**: oauth2client is now deprecated. As such, it is unlikely that we will 2 | review or merge to your pull request. We recommend you use 3 | [google-auth](https://google-auth.readthedocs.io) and [oauthlib](http://oauthlib.readthedocs.io/). 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build artifacts 2 | *.py[cod] 3 | oauth2client.egg-info/ 4 | build/ 5 | dist/ 6 | 7 | # Documentation-related 8 | docs/_build 9 | /google_appengine/ 10 | 11 | # Test files 12 | .tox/ 13 | .cache/ 14 | 15 | # Django test database 16 | db.sqlite3 17 | 18 | # Coverage files 19 | .coverage 20 | coverage.xml 21 | nosetests.xml 22 | htmlcov/ 23 | 24 | # Files with private / local data 25 | scripts/local_test_setup 26 | tests/data/key.json 27 | tests/data/key.p12 28 | tests/data/user-key.json 29 | 30 | # PyCharm configuration: 31 | .idea 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | 4 | matrix: 5 | include: 6 | - python: 2.7 7 | env: TOX_ENV=flake8 8 | - python: 2.7 9 | env: TOX_ENV=docs 10 | - python: 2.7 11 | env: TOX_ENV=gae 12 | - python: 2.7 13 | env: TOX_ENV=py27 14 | - python: 3.4 15 | env: TOX_ENV=py34 16 | - python: 3.5 17 | env: TOX_ENV=py35 18 | - python: 2.7 19 | env: TOX_ENV=system-tests 20 | - python: 3.4 21 | env: TOX_ENV=system-tests3 22 | - python: 2.7 23 | env: TOX_ENV=cover 24 | env: 25 | global: 26 | - GAE_PYTHONPATH=${HOME}/.cache/google_appengine 27 | cache: 28 | directories: 29 | - ${HOME}/.cache 30 | - ${HOME}/.pyenv 31 | install: 32 | - ./scripts/install.sh 33 | script: 34 | - ./scripts/run.sh 35 | after_success: 36 | - if [[ "${TOX_ENV}" == "cover" ]]; then coveralls; fi 37 | notifications: 38 | email: false 39 | 40 | deploy: 41 | provider: pypi 42 | user: gcloudpypi 43 | distributions: sdist bdist_wheel 44 | password: 45 | secure: "C9ImNa5kbdnrQNfX9ww4PUtQIr3tN+nfxl7eDkP1B8Qr0QNYjrjov7x+DLImkKvmoJd3dxYtYIpLE9esObUHu0gKHYxqymNHtuAAyoBOUfPtmp0vIEse9brNKMtaey5Ngk7ZWz9EHKBBqRHxqgN+Giby+K9Ta3K3urJIq6urYhE=" 46 | on: 47 | tags: true 48 | repo: google/oauth2client 49 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, 4 | and in the interest of fostering an open and welcoming community, 5 | we pledge to respect all people who contribute through reporting issues, 6 | posting feature requests, updating documentation, 7 | submitting pull requests or patches, and other activities. 8 | 9 | We are committed to making participation in this project 10 | a harassment-free experience for everyone, 11 | regardless of level of experience, gender, gender identity and expression, 12 | sexual orientation, disability, personal appearance, 13 | body size, race, ethnicity, age, religion, or nationality. 14 | 15 | Examples of unacceptable behavior by participants include: 16 | 17 | * The use of sexualized language or imagery 18 | * Personal attacks 19 | * Trolling or insulting/derogatory comments 20 | * Public or private harassment 21 | * Publishing other's private information, 22 | such as physical or electronic 23 | addresses, without explicit permission 24 | * Other unethical or unprofessional conduct. 25 | 26 | Project maintainers have the right and responsibility to remove, edit, or reject 27 | comments, commits, code, wiki edits, issues, and other contributions 28 | that are not aligned to this Code of Conduct. 29 | By adopting this Code of Conduct, 30 | project maintainers commit themselves to fairly and consistently 31 | applying these principles to every aspect of managing this project. 32 | Project maintainers who do not follow or enforce the Code of Conduct 33 | may be permanently removed from the project team. 34 | 35 | This code of conduct applies both within project spaces and in public spaces 36 | when an individual is representing the project or its community. 37 | 38 | Instances of abusive, harassing, or otherwise unacceptable behavior 39 | may be reported by opening an issue 40 | or contacting one or more of the project maintainers. 41 | 42 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, 43 | available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 44 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contribors to oauth2client 2 | 3 | ## Maintainers 4 | 5 | * [Nathaniel Manista](https://github.com/nathanielmanistaatgoogle) 6 | * [Jon Wayne Parrott](https://github.com/jonparrott) 7 | * [Danny Hermes](https://github.com/dhermes) 8 | 9 | Previous maintainers: 10 | 11 | * [Craig Citro](https://github.com/craigcitro) 12 | * [Joe Gregorio](https://github.com/jcgregorio) 13 | 14 | ## Contributors 15 | 16 | This list is generated from git commit authors. 17 | 18 | * aalexand 19 | * Aaron 20 | * Adam Chainz 21 | * ade@google.com 22 | * Alexandre Vivien 23 | * Ali Afshar 24 | * Andrzej Pragacz 25 | * api.nickm@gmail.com 26 | * Ben Demaree 27 | * Bill Prin 28 | * Brendan McCollam 29 | * Craig Citro 30 | * Dan Ring 31 | * Daniel Hermes 32 | * Danilo Akamine 33 | * daryl herzmann 34 | * dlorenc 35 | * Dominik Miedziński 36 | * dr. Kertész Csaba-Zoltán 37 | * Dustin Farris 38 | * Eddie Warner 39 | * Edwin Amsler 40 | * elibixby 41 | * Emanuele Pucciarelli 42 | * Eric Koleda 43 | * Frederik Creemers 44 | * Guido van Rossum 45 | * Harsh Vardhan 46 | * Herr Kaste 47 | * INADA Naoki 48 | * JacobMoshenko 49 | * Jay Lee 50 | * Jed Hartman 51 | * Jeff Terrace 52 | * Jeffrey Sorensen 53 | * Jeremi Joslin 54 | * Jin Liu 55 | * Joe Beda 56 | * Joe Gregorio 57 | * Johan Euphrosine 58 | * John Asmuth 59 | * John Vandenberg 60 | * Jon Wayne Parrott 61 | * Jose Alcerreca 62 | * KCs 63 | * Keith Maxwell 64 | * Ken Payson 65 | * Kevin Regan 66 | * lraccomando 67 | * Luar Roji 68 | * Luke Blanshard 69 | * Marc Cohen 70 | * Mark Pellegrini 71 | * Martin Trigaux 72 | * Matt McDonald 73 | * Nathan Naze 74 | * Nathaniel Manista 75 | * Orest Bolohan 76 | * Pat Ferate 77 | * Patrick Costello 78 | * Rafe Kaplan 79 | * rahulpaul@google.com 80 | * RM Saksida 81 | * Robert Kaplow 82 | * Robert Spies 83 | * Sergei Trofimovich 84 | * sgomes@google.com 85 | * Simon Cadman 86 | * soltanmm 87 | * Sébastien de Melo 88 | * takuya sato 89 | * thobrla 90 | * Tom Miller 91 | * Tony Aiuto 92 | * Travis Hobrla 93 | * Veres Lajos 94 | * Vivek Seth 95 | * Éamonn McManus 96 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE CHANGELOG.md 2 | recursive-include tests * 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | tox 3 | 4 | docs: 5 | scripts/doc-build 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/google/oauth2client.svg?branch=master)](https://travis-ci.org/google/oauth2client) 2 | [![Coverage Status](https://coveralls.io/repos/google/oauth2client/badge.svg?branch=master&service=github)](https://coveralls.io/github/google/oauth2client?branch=master) 3 | [![Documentation Status](https://readthedocs.org/projects/oauth2client/badge/?version=latest)](https://oauth2client.readthedocs.io/) 4 | 5 | This is a client library for accessing resources protected by OAuth 2.0. 6 | 7 | **Note**: oauth2client is now deprecated. No more features will be added to the 8 | libraries and the core team is turning down support. We recommend you use 9 | [google-auth](https://google-auth.readthedocs.io) and [oauthlib](http://oauthlib.readthedocs.io/). For more details on the deprecation, see [oauth2client deprecation](https://google-auth.readthedocs.io/en/latest/oauth2client-deprecation.html). 10 | 11 | Installation 12 | ============ 13 | 14 | To install, simply run the following command in your terminal: 15 | 16 | ```bash 17 | $ pip install --upgrade oauth2client 18 | ``` 19 | 20 | Contributing 21 | ============ 22 | 23 | Please see the [CONTRIBUTING page][1] for more information. In particular, we 24 | love pull requests -- but please make sure to sign the contributor license 25 | agreement. 26 | 27 | Supported Python Versions 28 | ========================= 29 | 30 | We support Python 2.7 and 3.4+. More information [in the docs][2]. 31 | 32 | [1]: https://github.com/google/oauth2client/blob/master/CONTRIBUTING.md 33 | [2]: https://oauth2client.readthedocs.io/#supported-python-versions 34 | -------------------------------------------------------------------------------- /docs/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/oauth2client/50d20532a748f18e53f7d24ccbe6647132c979a9/docs/_static/favicon.ico -------------------------------------------------------------------------------- /docs/_static/google_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/oauth2client/50d20532a748f18e53f7d24ccbe6647132c979a9/docs/_static/google_logo.png -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # oauth2client documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Dec 17 23:13:19 2014. 5 | # 6 | 7 | import os 8 | import sys 9 | 10 | 11 | # In order to load django before 1.7, we need to create a faux 12 | # settings module and load it. This assumes django has been installed 13 | # (but it must be for the docs to build), so if it has not already 14 | # been installed run `pip install -r docs/requirements.txt`. 15 | os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.contrib.django_util.settings' 16 | import django 17 | import mock 18 | from pkg_resources import get_distribution 19 | if django.VERSION[1] < 7: 20 | sys.path.insert(0, '.') 21 | 22 | # See https://read-the-docs.readthedocs.io/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules 23 | 24 | 25 | class Mock(mock.Mock): 26 | 27 | @classmethod 28 | def __getattr__(cls, name): 29 | return Mock() 30 | 31 | 32 | MOCK_MODULES = ( 33 | 'google', 34 | 'google.appengine', 35 | 'google.appengine.api', 36 | 'google.appengine.api.app_identiy', 37 | 'google.appengine.api.urlfetch', 38 | 'google.appengine.ext', 39 | 'google.appengine.ext.webapp', 40 | 'google.appengine.ext.webapp.util', 41 | 'werkzeug.local', 42 | ) 43 | 44 | 45 | # If extensions (or modules to document with autodoc) are in another directory, 46 | # add these directories to sys.path here. If the directory is relative to the 47 | # documentation root, use os.path.abspath to make it absolute, like shown here. 48 | sys.path.insert(0, os.path.abspath('..')) 49 | 50 | # -- General configuration ------------------------------------------------ 51 | 52 | extensions = [ 53 | 'sphinx.ext.autodoc', 54 | 'sphinx.ext.coverage', 55 | 'sphinx.ext.napoleon', 56 | 'sphinx.ext.viewcode', 57 | ] 58 | templates_path = ['_templates'] 59 | source_suffix = '.rst' 60 | master_doc = 'index' 61 | 62 | # General information about the project. 63 | project = u'oauth2client' 64 | copyright = u'2014, Google, Inc' 65 | 66 | # Version info 67 | distro = get_distribution('oauth2client') 68 | version = distro.version 69 | release = distro.version 70 | 71 | exclude_patterns = ['_build'] 72 | 73 | # -- Options for HTML output ---------------------------------------------- 74 | 75 | # We fake our more expensive imports when building the docs. 76 | sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) 77 | 78 | # We want to set the RTD theme, but not if we're on RTD. 79 | if os.environ.get('READTHEDOCS', None) != 'True': 80 | import sphinx_rtd_theme 81 | html_theme = 'sphinx_rtd_theme' 82 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 83 | 84 | # The name of an image file (within the static path) to use as favicon of the 85 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 86 | # pixels large. 87 | html_favicon = '_static/favicon.ico' 88 | 89 | html_static_path = ['_static'] 90 | html_logo = '_static/google_logo.png' 91 | htmlhelp_basename = 'oauth2clientdoc' 92 | 93 | # -- Options for LaTeX output --------------------------------------------- 94 | 95 | latex_elements = {} 96 | latex_documents = [ 97 | ('index', 'oauth2client.tex', u'oauth2client Documentation', 98 | u'Google, Inc.', 'manual'), 99 | ] 100 | 101 | # -- Options for manual page output --------------------------------------- 102 | 103 | man_pages = [ 104 | ('index', 'oauth2client', u'oauth2client Documentation', 105 | [u'Google, Inc.'], 1) 106 | ] 107 | 108 | # -- Options for Texinfo output ------------------------------------------- 109 | 110 | texinfo_documents = [ 111 | ('index', 'oauth2client', u'oauth2client Documentation', 112 | u'Google, Inc.', 'oauth2client', 'One line description of project.', 113 | 'Miscellaneous'), 114 | ] 115 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | oauth2client 2 | ============ 3 | 4 | .. note:: oauth2client is now deprecated. No more features will be added to the 5 | libraries and the core team is turning down support. We recommend you use 6 | `google-auth`_ and `oauthlib`_. For more details on the deprecation, see `oauth2client deprecation`_. 7 | 8 | .. _google-auth: https://google-auth.readthedocs.io 9 | .. _oauthlib: http://oauthlib.readthedocs.io/ 10 | .. _oauth2client deprecation: https://google-auth.readthedocs.io/en/latest/oauth2client-deprecation.html 11 | 12 | *making OAuth2 just a little less painful* 13 | 14 | ``oauth2client`` makes it easy to interact with OAuth2-protected resources, 15 | especially those related to Google APIs. You can also start with `general 16 | information about using OAuth2 with Google APIs 17 | `_. 18 | 19 | Getting started 20 | --------------- 21 | 22 | We recommend installing via ``pip``: 23 | 24 | .. code-block:: bash 25 | 26 | $ pip install --upgrade oauth2client 27 | 28 | You can also install from source: 29 | 30 | .. code-block:: bash 31 | 32 | $ git clone https://github.com/google/oauth2client 33 | $ cd oauth2client 34 | $ python setup.py install 35 | 36 | Using ``pypy`` 37 | -------------- 38 | 39 | - In order to use crypto libraries (e.g. for service accounts) you will 40 | need to install one of ``pycrypto`` or ``pyOpenSSL``. 41 | - Using ``pycrypto`` with ``pypy`` will be in general problematic. If 42 | ``libgmp`` is installed on your machine, the ``pycrypto`` install will 43 | attempt to build ``_fastmath.c``. However, this file uses CPython 44 | implementation details and hence can't be built in ``pypy`` (as of 45 | ``pypy`` 2.6 and ``pycrypto`` 2.6.1). In order to install 46 | 47 | .. code-block:: bash 48 | 49 | with_gmp=no pip install --upgrade pycrypto 50 | 51 | See discussions on the `pypy issue tracker`_ and the 52 | `pycrypto issue tracker`_. 53 | 54 | - Using ``pyOpenSSL`` with versions of ``pypy`` before 2.6 may be in general 55 | problematic since ``pyOpenSSL`` depends on the ``cryptography`` library. 56 | For versions of ``cryptography`` before 1.0, importing ``pyOpenSSL`` 57 | with it caused `massive startup costs`_. In order to address this 58 | slow startup, ``cryptography`` 1.0 made some `changes`_ in how it used 59 | ``cffi`` when means it can't be used on versions of ``pypy`` before 2.6. 60 | 61 | The default version of ``pypy`` you get when installed 62 | 63 | .. code-block:: bash 64 | 65 | apt-get install pypy pypy-dev 66 | 67 | on `Ubuntu 14.04`_ is 2.2.1. In order to upgrade, you'll need to use 68 | the `pypy/ppa PPA`_: 69 | 70 | .. code-block:: bash 71 | 72 | apt-get purge pypy pypy-dev 73 | add-apt-repository ppa:pypy/ppa 74 | apt-get update 75 | apt-get install pypy pypy-dev 76 | 77 | .. _pypy issue tracker: https://bitbucket.org/pypy/pypy/issues/997 78 | .. _pycrypto issue tracker: https://github.com/dlitz/pycrypto/pull/59 79 | .. _massive startup costs: https://github.com/pyca/pyopenssl/issues/137 80 | .. _changes: https://github.com/pyca/cryptography/issues/2275#issuecomment-130751514 81 | .. _Ubuntu 14.04: http://packages.ubuntu.com/trusty/pypy 82 | .. _pypy/ppa PPA: https://launchpad.net/~pypy/+archive/ubuntu/ppa 83 | 84 | Downloads 85 | ^^^^^^^^^ 86 | 87 | * `Most recent release tarball 88 | `_ 89 | * `Most recent release zipfile 90 | `_ 91 | * `Complete release list `_ 92 | 93 | Library Documentation 94 | --------------------- 95 | 96 | * Complete library index: :ref:`genindex` 97 | * Index of all modules: :ref:`modindex` 98 | * Search all documentation: :ref:`search` 99 | 100 | Contributing 101 | ------------ 102 | 103 | Please see the `contributing page`_ for more information. 104 | In particular, we love pull requests -- but please make sure to sign the 105 | contributor license agreement. 106 | 107 | .. _contributing page: https://github.com/google/oauth2client/blob/master/CONTRIBUTING.md 108 | 109 | .. toctree:: 110 | :maxdepth: 1 111 | :hidden: 112 | 113 | source/oauth2client 114 | 115 | Supported Python Versions 116 | ------------------------- 117 | 118 | We support Python 2.7 and 3.4+. (Whatever this file says, the truth is 119 | always represented by our `tox.ini`_). 120 | 121 | .. _tox.ini: https://github.com/google/oauth2client/blob/master/tox.ini 122 | 123 | We explicitly decided to support Python 3 beginning with version 124 | 3.4. Reasons for this include: 125 | 126 | * Encouraging use of newest versions of Python 3 127 | * Following the lead of prominent `open-source projects`_ 128 | * Unicode literal support which 129 | allows for a cleaner codebase that works in both Python 2 and Python 3 130 | * Prominent projects like `django`_ have `dropped support`_ for earlier 131 | versions (3.3 support dropped in December 2015, and 2.6 support 132 | `dropped`_ in September 2014) 133 | 134 | .. _open-source projects: http://docs.python-requests.org/en/latest/ 135 | .. _Unicode literal support: https://www.python.org/dev/peps/pep-0414/ 136 | .. _django: https://docs.djangoproject.com/ 137 | .. _dropped support: https://docs.djangoproject.com/en/1.9/faq/install/#what-python-version-can-i-use-with-django 138 | .. _dropped: https://docs.djangoproject.com/en/1.7/faq/install/#what-python-version-can-i-use-with-django 139 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | django 2 | flask 3 | keyring 4 | mock 5 | pycrypto>=2.6 6 | pyopenssl>=0.14 7 | python-gflags 8 | pyyaml 9 | webapp2 10 | WebOb 11 | -------------------------------------------------------------------------------- /docs/source/oauth2client.client.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.client module 2 | =========================== 3 | 4 | .. automodule:: oauth2client.client 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.clientsecrets.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.clientsecrets module 2 | ================================== 3 | 4 | .. automodule:: oauth2client.clientsecrets 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.appengine.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.appengine module 2 | ======================================= 3 | 4 | .. automodule:: oauth2client.contrib.appengine 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.devshell.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.devshell module 2 | ====================================== 3 | 4 | .. automodule:: oauth2client.contrib.devshell 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.dictionary_storage.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.dictionary\_storage module 2 | ================================================= 3 | 4 | .. automodule:: oauth2client.contrib.dictionary_storage 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.django_util.apps.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.django\_util\.apps module 2 | ================================================ 3 | 4 | .. automodule:: oauth2client.contrib.django_util.apps 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.django_util.decorators.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.django\_util\.decorators module 2 | ====================================================== 3 | 4 | .. automodule:: oauth2client.contrib.django_util.decorators 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.django_util.models.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.django\_util\.models module 2 | ================================================== 3 | 4 | .. automodule:: oauth2client.contrib.django_util.models 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.django_util.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.django\_util package 2 | =========================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | .. toctree:: 8 | 9 | oauth2client.contrib.django_util.apps 10 | oauth2client.contrib.django_util.decorators 11 | oauth2client.contrib.django_util.models 12 | oauth2client.contrib.django_util.signals 13 | oauth2client.contrib.django_util.site 14 | oauth2client.contrib.django_util.storage 15 | oauth2client.contrib.django_util.views 16 | 17 | Module contents 18 | --------------- 19 | 20 | .. automodule:: oauth2client.contrib.django_util 21 | :members: 22 | :undoc-members: 23 | :show-inheritance: 24 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.django_util.signals.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.django\_util\.signals module 2 | =================================================== 3 | 4 | .. automodule:: oauth2client.contrib.django_util.signals 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.django_util.site.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.django\_util\.site module 2 | ================================================ 3 | 4 | .. automodule:: oauth2client.contrib.django_util.site 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.django_util.storage.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.django\_util\.storage module 2 | =================================================== 3 | 4 | .. automodule:: oauth2client.contrib.django_util.storage 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.django_util.views.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.django\_util\.views module 2 | ================================================= 3 | 4 | .. automodule:: oauth2client.contrib.django_util.views 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.flask_util.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.flask\_util module 2 | ========================================= 3 | 4 | .. automodule:: oauth2client.contrib.flask_util 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.gce.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.gce module 2 | ================================= 3 | 4 | .. automodule:: oauth2client.contrib.gce 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.keyring_storage.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.keyring\_storage module 2 | ============================================== 3 | 4 | .. automodule:: oauth2client.contrib.keyring_storage 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.multiprocess_file_storage.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.multiprocess\_file\_storage module 2 | ========================================================= 3 | 4 | .. automodule:: oauth2client.contrib.multiprocess_file_storage 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib package 2 | ============================= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | oauth2client.contrib.django_util 10 | 11 | Submodules 12 | ---------- 13 | 14 | .. toctree:: 15 | 16 | oauth2client.contrib.appengine 17 | oauth2client.contrib.devshell 18 | oauth2client.contrib.dictionary_storage 19 | oauth2client.contrib.flask_util 20 | oauth2client.contrib.gce 21 | oauth2client.contrib.keyring_storage 22 | oauth2client.contrib.multiprocess_file_storage 23 | oauth2client.contrib.sqlalchemy 24 | oauth2client.contrib.xsrfutil 25 | 26 | Module contents 27 | --------------- 28 | 29 | .. automodule:: oauth2client.contrib 30 | :members: 31 | :undoc-members: 32 | :show-inheritance: 33 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.sqlalchemy.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.sqlalchemy module 2 | ======================================== 3 | 4 | .. automodule:: oauth2client.contrib.sqlalchemy 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.contrib.xsrfutil.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.contrib\.xsrfutil module 2 | ====================================== 3 | 4 | .. automodule:: oauth2client.contrib.xsrfutil 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.crypt.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.crypt module 2 | ========================== 3 | 4 | .. automodule:: oauth2client.crypt 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.file.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.file module 2 | ========================= 3 | 4 | .. automodule:: oauth2client.file 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.rst: -------------------------------------------------------------------------------- 1 | oauth2client package 2 | ==================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | oauth2client.contrib 10 | 11 | Submodules 12 | ---------- 13 | 14 | .. toctree:: 15 | 16 | oauth2client.client 17 | oauth2client.clientsecrets 18 | oauth2client.crypt 19 | oauth2client.file 20 | oauth2client.service_account 21 | oauth2client.tools 22 | oauth2client.transport 23 | 24 | Module contents 25 | --------------- 26 | 27 | .. automodule:: oauth2client 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | -------------------------------------------------------------------------------- /docs/source/oauth2client.service_account.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.service\_account module 2 | ===================================== 3 | 4 | .. automodule:: oauth2client.service_account 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.tools.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.tools module 2 | ========================== 3 | 4 | .. automodule:: oauth2client.tools 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/oauth2client.transport.rst: -------------------------------------------------------------------------------- 1 | oauth2client\.transport module 2 | ============================== 3 | 4 | .. automodule:: oauth2client.transport 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /oauth2client/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Client library for using OAuth2, especially with Google APIs.""" 16 | 17 | __version__ = '4.1.3' 18 | 19 | GOOGLE_AUTH_URI = 'https://accounts.google.com/o/oauth2/v2/auth' 20 | GOOGLE_DEVICE_URI = 'https://oauth2.googleapis.com/device/code' 21 | GOOGLE_REVOKE_URI = 'https://oauth2.googleapis.com/revoke' 22 | GOOGLE_TOKEN_URI = 'https://oauth2.googleapis.com/token' 23 | GOOGLE_TOKEN_INFO_URI = 'https://oauth2.googleapis.com/tokeninfo' 24 | 25 | -------------------------------------------------------------------------------- /oauth2client/_openssl_crypt.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """OpenSSL Crypto-related routines for oauth2client.""" 15 | 16 | from OpenSSL import crypto 17 | 18 | from oauth2client import _helpers 19 | 20 | 21 | class OpenSSLVerifier(object): 22 | """Verifies the signature on a message.""" 23 | 24 | def __init__(self, pubkey): 25 | """Constructor. 26 | 27 | Args: 28 | pubkey: OpenSSL.crypto.PKey, The public key to verify with. 29 | """ 30 | self._pubkey = pubkey 31 | 32 | def verify(self, message, signature): 33 | """Verifies a message against a signature. 34 | 35 | Args: 36 | message: string or bytes, The message to verify. If string, will be 37 | encoded to bytes as utf-8. 38 | signature: string or bytes, The signature on the message. If string, 39 | will be encoded to bytes as utf-8. 40 | 41 | Returns: 42 | True if message was signed by the private key associated with the 43 | public key that this object was constructed with. 44 | """ 45 | message = _helpers._to_bytes(message, encoding='utf-8') 46 | signature = _helpers._to_bytes(signature, encoding='utf-8') 47 | try: 48 | crypto.verify(self._pubkey, signature, message, 'sha256') 49 | return True 50 | except crypto.Error: 51 | return False 52 | 53 | @staticmethod 54 | def from_string(key_pem, is_x509_cert): 55 | """Construct a Verified instance from a string. 56 | 57 | Args: 58 | key_pem: string, public key in PEM format. 59 | is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it 60 | is expected to be an RSA key in PEM format. 61 | 62 | Returns: 63 | Verifier instance. 64 | 65 | Raises: 66 | OpenSSL.crypto.Error: if the key_pem can't be parsed. 67 | """ 68 | key_pem = _helpers._to_bytes(key_pem) 69 | if is_x509_cert: 70 | pubkey = crypto.load_certificate(crypto.FILETYPE_PEM, key_pem) 71 | else: 72 | pubkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key_pem) 73 | return OpenSSLVerifier(pubkey) 74 | 75 | 76 | class OpenSSLSigner(object): 77 | """Signs messages with a private key.""" 78 | 79 | def __init__(self, pkey): 80 | """Constructor. 81 | 82 | Args: 83 | pkey: OpenSSL.crypto.PKey (or equiv), The private key to sign with. 84 | """ 85 | self._key = pkey 86 | 87 | def sign(self, message): 88 | """Signs a message. 89 | 90 | Args: 91 | message: bytes, Message to be signed. 92 | 93 | Returns: 94 | string, The signature of the message for the given key. 95 | """ 96 | message = _helpers._to_bytes(message, encoding='utf-8') 97 | return crypto.sign(self._key, message, 'sha256') 98 | 99 | @staticmethod 100 | def from_string(key, password=b'notasecret'): 101 | """Construct a Signer instance from a string. 102 | 103 | Args: 104 | key: string, private key in PKCS12 or PEM format. 105 | password: string, password for the private key file. 106 | 107 | Returns: 108 | Signer instance. 109 | 110 | Raises: 111 | OpenSSL.crypto.Error if the key can't be parsed. 112 | """ 113 | key = _helpers._to_bytes(key) 114 | parsed_pem_key = _helpers._parse_pem_key(key) 115 | if parsed_pem_key: 116 | pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key) 117 | else: 118 | password = _helpers._to_bytes(password, encoding='utf-8') 119 | pkey = crypto.load_pkcs12(key, password).get_privatekey() 120 | return OpenSSLSigner(pkey) 121 | 122 | 123 | def pkcs12_key_as_pem(private_key_bytes, private_key_password): 124 | """Convert the contents of a PKCS#12 key to PEM using pyOpenSSL. 125 | 126 | Args: 127 | private_key_bytes: Bytes. PKCS#12 key in DER format. 128 | private_key_password: String. Password for PKCS#12 key. 129 | 130 | Returns: 131 | String. PEM contents of ``private_key_bytes``. 132 | """ 133 | private_key_password = _helpers._to_bytes(private_key_password) 134 | pkcs12 = crypto.load_pkcs12(private_key_bytes, private_key_password) 135 | return crypto.dump_privatekey(crypto.FILETYPE_PEM, 136 | pkcs12.get_privatekey()) 137 | -------------------------------------------------------------------------------- /oauth2client/_pkce.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Utility functions for implementing Proof Key for Code Exchange (PKCE) by OAuth 17 | Public Clients 18 | 19 | See RFC7636. 20 | """ 21 | 22 | import base64 23 | import hashlib 24 | import os 25 | 26 | 27 | def code_verifier(n_bytes=64): 28 | """ 29 | Generates a 'code_verifier' as described in section 4.1 of RFC 7636. 30 | 31 | This is a 'high-entropy cryptographic random string' that will be 32 | impractical for an attacker to guess. 33 | 34 | Args: 35 | n_bytes: integer between 31 and 96, inclusive. default: 64 36 | number of bytes of entropy to include in verifier. 37 | 38 | Returns: 39 | Bytestring, representing urlsafe base64-encoded random data. 40 | """ 41 | verifier = base64.urlsafe_b64encode(os.urandom(n_bytes)).rstrip(b'=') 42 | # https://tools.ietf.org/html/rfc7636#section-4.1 43 | # minimum length of 43 characters and a maximum length of 128 characters. 44 | if len(verifier) < 43: 45 | raise ValueError("Verifier too short. n_bytes must be > 30.") 46 | elif len(verifier) > 128: 47 | raise ValueError("Verifier too long. n_bytes must be < 97.") 48 | else: 49 | return verifier 50 | 51 | 52 | def code_challenge(verifier): 53 | """ 54 | Creates a 'code_challenge' as described in section 4.2 of RFC 7636 55 | by taking the sha256 hash of the verifier and then urlsafe 56 | base64-encoding it. 57 | 58 | Args: 59 | verifier: bytestring, representing a code_verifier as generated by 60 | code_verifier(). 61 | 62 | Returns: 63 | Bytestring, representing a urlsafe base64-encoded sha256 hash digest, 64 | without '=' padding. 65 | """ 66 | digest = hashlib.sha256(verifier).digest() 67 | return base64.urlsafe_b64encode(digest).rstrip(b'=') 68 | -------------------------------------------------------------------------------- /oauth2client/_pycrypto_crypt.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """pyCrypto Crypto-related routines for oauth2client.""" 15 | 16 | from Crypto.Hash import SHA256 17 | from Crypto.PublicKey import RSA 18 | from Crypto.Signature import PKCS1_v1_5 19 | from Crypto.Util.asn1 import DerSequence 20 | 21 | from oauth2client import _helpers 22 | 23 | 24 | class PyCryptoVerifier(object): 25 | """Verifies the signature on a message.""" 26 | 27 | def __init__(self, pubkey): 28 | """Constructor. 29 | 30 | Args: 31 | pubkey: OpenSSL.crypto.PKey (or equiv), The public key to verify 32 | with. 33 | """ 34 | self._pubkey = pubkey 35 | 36 | def verify(self, message, signature): 37 | """Verifies a message against a signature. 38 | 39 | Args: 40 | message: string or bytes, The message to verify. If string, will be 41 | encoded to bytes as utf-8. 42 | signature: string or bytes, The signature on the message. 43 | 44 | Returns: 45 | True if message was signed by the private key associated with the 46 | public key that this object was constructed with. 47 | """ 48 | message = _helpers._to_bytes(message, encoding='utf-8') 49 | return PKCS1_v1_5.new(self._pubkey).verify( 50 | SHA256.new(message), signature) 51 | 52 | @staticmethod 53 | def from_string(key_pem, is_x509_cert): 54 | """Construct a Verified instance from a string. 55 | 56 | Args: 57 | key_pem: string, public key in PEM format. 58 | is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it 59 | is expected to be an RSA key in PEM format. 60 | 61 | Returns: 62 | Verifier instance. 63 | """ 64 | if is_x509_cert: 65 | key_pem = _helpers._to_bytes(key_pem) 66 | pemLines = key_pem.replace(b' ', b'').split() 67 | certDer = _helpers._urlsafe_b64decode(b''.join(pemLines[1:-1])) 68 | certSeq = DerSequence() 69 | certSeq.decode(certDer) 70 | tbsSeq = DerSequence() 71 | tbsSeq.decode(certSeq[0]) 72 | pubkey = RSA.importKey(tbsSeq[6]) 73 | else: 74 | pubkey = RSA.importKey(key_pem) 75 | return PyCryptoVerifier(pubkey) 76 | 77 | 78 | class PyCryptoSigner(object): 79 | """Signs messages with a private key.""" 80 | 81 | def __init__(self, pkey): 82 | """Constructor. 83 | 84 | Args: 85 | pkey, OpenSSL.crypto.PKey (or equiv), The private key to sign with. 86 | """ 87 | self._key = pkey 88 | 89 | def sign(self, message): 90 | """Signs a message. 91 | 92 | Args: 93 | message: string, Message to be signed. 94 | 95 | Returns: 96 | string, The signature of the message for the given key. 97 | """ 98 | message = _helpers._to_bytes(message, encoding='utf-8') 99 | return PKCS1_v1_5.new(self._key).sign(SHA256.new(message)) 100 | 101 | @staticmethod 102 | def from_string(key, password='notasecret'): 103 | """Construct a Signer instance from a string. 104 | 105 | Args: 106 | key: string, private key in PEM format. 107 | password: string, password for private key file. Unused for PEM 108 | files. 109 | 110 | Returns: 111 | Signer instance. 112 | 113 | Raises: 114 | NotImplementedError if the key isn't in PEM format. 115 | """ 116 | parsed_pem_key = _helpers._parse_pem_key(_helpers._to_bytes(key)) 117 | if parsed_pem_key: 118 | pkey = RSA.importKey(parsed_pem_key) 119 | else: 120 | raise NotImplementedError( 121 | 'No key in PEM format was detected. This implementation ' 122 | 'can only use the PyCrypto library for keys in PEM ' 123 | 'format.') 124 | return PyCryptoSigner(pkey) 125 | -------------------------------------------------------------------------------- /oauth2client/clientsecrets.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utilities for reading OAuth 2.0 client secret files. 16 | 17 | A client_secrets.json file contains all the information needed to interact with 18 | an OAuth 2.0 protected service. 19 | """ 20 | 21 | import json 22 | 23 | import six 24 | 25 | 26 | # Properties that make a client_secrets.json file valid. 27 | TYPE_WEB = 'web' 28 | TYPE_INSTALLED = 'installed' 29 | 30 | VALID_CLIENT = { 31 | TYPE_WEB: { 32 | 'required': [ 33 | 'client_id', 34 | 'client_secret', 35 | 'redirect_uris', 36 | 'auth_uri', 37 | 'token_uri', 38 | ], 39 | 'string': [ 40 | 'client_id', 41 | 'client_secret', 42 | ], 43 | }, 44 | TYPE_INSTALLED: { 45 | 'required': [ 46 | 'client_id', 47 | 'client_secret', 48 | 'redirect_uris', 49 | 'auth_uri', 50 | 'token_uri', 51 | ], 52 | 'string': [ 53 | 'client_id', 54 | 'client_secret', 55 | ], 56 | }, 57 | } 58 | 59 | 60 | class Error(Exception): 61 | """Base error for this module.""" 62 | 63 | 64 | class InvalidClientSecretsError(Error): 65 | """Format of ClientSecrets file is invalid.""" 66 | 67 | 68 | def _validate_clientsecrets(clientsecrets_dict): 69 | """Validate parsed client secrets from a file. 70 | 71 | Args: 72 | clientsecrets_dict: dict, a dictionary holding the client secrets. 73 | 74 | Returns: 75 | tuple, a string of the client type and the information parsed 76 | from the file. 77 | """ 78 | _INVALID_FILE_FORMAT_MSG = ( 79 | 'Invalid file format. See ' 80 | 'https://developers.google.com/api-client-library/' 81 | 'python/guide/aaa_client_secrets') 82 | 83 | if clientsecrets_dict is None: 84 | raise InvalidClientSecretsError(_INVALID_FILE_FORMAT_MSG) 85 | try: 86 | (client_type, client_info), = clientsecrets_dict.items() 87 | except (ValueError, AttributeError): 88 | raise InvalidClientSecretsError( 89 | _INVALID_FILE_FORMAT_MSG + ' ' 90 | 'Expected a JSON object with a single property for a "web" or ' 91 | '"installed" application') 92 | 93 | if client_type not in VALID_CLIENT: 94 | raise InvalidClientSecretsError( 95 | 'Unknown client type: {0}.'.format(client_type)) 96 | 97 | for prop_name in VALID_CLIENT[client_type]['required']: 98 | if prop_name not in client_info: 99 | raise InvalidClientSecretsError( 100 | 'Missing property "{0}" in a client type of "{1}".'.format( 101 | prop_name, client_type)) 102 | for prop_name in VALID_CLIENT[client_type]['string']: 103 | if client_info[prop_name].startswith('[['): 104 | raise InvalidClientSecretsError( 105 | 'Property "{0}" is not configured.'.format(prop_name)) 106 | return client_type, client_info 107 | 108 | 109 | def load(fp): 110 | obj = json.load(fp) 111 | return _validate_clientsecrets(obj) 112 | 113 | 114 | def loads(s): 115 | obj = json.loads(s) 116 | return _validate_clientsecrets(obj) 117 | 118 | 119 | def _loadfile(filename): 120 | try: 121 | with open(filename, 'r') as fp: 122 | obj = json.load(fp) 123 | except IOError as exc: 124 | raise InvalidClientSecretsError('Error opening file', exc.filename, 125 | exc.strerror, exc.errno) 126 | return _validate_clientsecrets(obj) 127 | 128 | 129 | def loadfile(filename, cache=None): 130 | """Loading of client_secrets JSON file, optionally backed by a cache. 131 | 132 | Typical cache storage would be App Engine memcache service, 133 | but you can pass in any other cache client that implements 134 | these methods: 135 | 136 | * ``get(key, namespace=ns)`` 137 | * ``set(key, value, namespace=ns)`` 138 | 139 | Usage:: 140 | 141 | # without caching 142 | client_type, client_info = loadfile('secrets.json') 143 | # using App Engine memcache service 144 | from google.appengine.api import memcache 145 | client_type, client_info = loadfile('secrets.json', cache=memcache) 146 | 147 | Args: 148 | filename: string, Path to a client_secrets.json file on a filesystem. 149 | cache: An optional cache service client that implements get() and set() 150 | methods. If not specified, the file is always being loaded from 151 | a filesystem. 152 | 153 | Raises: 154 | InvalidClientSecretsError: In case of a validation error or some 155 | I/O failure. Can happen only on cache miss. 156 | 157 | Returns: 158 | (client_type, client_info) tuple, as _loadfile() normally would. 159 | JSON contents is validated only during first load. Cache hits are not 160 | validated. 161 | """ 162 | _SECRET_NAMESPACE = 'oauth2client:secrets#ns' 163 | 164 | if not cache: 165 | return _loadfile(filename) 166 | 167 | obj = cache.get(filename, namespace=_SECRET_NAMESPACE) 168 | if obj is None: 169 | client_type, client_info = _loadfile(filename) 170 | obj = {client_type: client_info} 171 | cache.set(filename, obj, namespace=_SECRET_NAMESPACE) 172 | 173 | return next(six.iteritems(obj)) 174 | -------------------------------------------------------------------------------- /oauth2client/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | """Contributed modules. 2 | 3 | Contrib contains modules that are not considered part of the core oauth2client 4 | library but provide additional functionality. These modules are intended to 5 | make it easier to use oauth2client. 6 | """ 7 | -------------------------------------------------------------------------------- /oauth2client/contrib/_appengine_ndb.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Google App Engine utilities helper. 16 | 17 | Classes that directly require App Engine's ndb library. Provided 18 | as a separate module in case of failure to import ndb while 19 | other App Engine libraries are present. 20 | """ 21 | 22 | import logging 23 | 24 | from google.appengine.ext import ndb 25 | 26 | from oauth2client import client 27 | 28 | 29 | NDB_KEY = ndb.Key 30 | """Key constant used by :mod:`oauth2client.contrib.appengine`.""" 31 | 32 | NDB_MODEL = ndb.Model 33 | """Model constant used by :mod:`oauth2client.contrib.appengine`.""" 34 | 35 | _LOGGER = logging.getLogger(__name__) 36 | 37 | 38 | class SiteXsrfSecretKeyNDB(ndb.Model): 39 | """NDB Model for storage for the sites XSRF secret key. 40 | 41 | Since this model uses the same kind as SiteXsrfSecretKey, it can be 42 | used interchangeably. This simply provides an NDB model for interacting 43 | with the same data the DB model interacts with. 44 | 45 | There should only be one instance stored of this model, the one used 46 | for the site. 47 | """ 48 | secret = ndb.StringProperty() 49 | 50 | @classmethod 51 | def _get_kind(cls): 52 | """Return the kind name for this class.""" 53 | return 'SiteXsrfSecretKey' 54 | 55 | 56 | class FlowNDBProperty(ndb.PickleProperty): 57 | """App Engine NDB datastore Property for Flow. 58 | 59 | Serves the same purpose as the DB FlowProperty, but for NDB models. 60 | Since PickleProperty inherits from BlobProperty, the underlying 61 | representation of the data in the datastore will be the same as in the 62 | DB case. 63 | 64 | Utility property that allows easy storage and retrieval of an 65 | oauth2client.Flow 66 | """ 67 | 68 | def _validate(self, value): 69 | """Validates a value as a proper Flow object. 70 | 71 | Args: 72 | value: A value to be set on the property. 73 | 74 | Raises: 75 | TypeError if the value is not an instance of Flow. 76 | """ 77 | _LOGGER.info('validate: Got type %s', type(value)) 78 | if value is not None and not isinstance(value, client.Flow): 79 | raise TypeError( 80 | 'Property {0} must be convertible to a flow ' 81 | 'instance; received: {1}.'.format(self._name, value)) 82 | 83 | 84 | class CredentialsNDBProperty(ndb.BlobProperty): 85 | """App Engine NDB datastore Property for Credentials. 86 | 87 | Serves the same purpose as the DB CredentialsProperty, but for NDB 88 | models. Since CredentialsProperty stores data as a blob and this 89 | inherits from BlobProperty, the data in the datastore will be the same 90 | as in the DB case. 91 | 92 | Utility property that allows easy storage and retrieval of Credentials 93 | and subclasses. 94 | """ 95 | 96 | def _validate(self, value): 97 | """Validates a value as a proper credentials object. 98 | 99 | Args: 100 | value: A value to be set on the property. 101 | 102 | Raises: 103 | TypeError if the value is not an instance of Credentials. 104 | """ 105 | _LOGGER.info('validate: Got type %s', type(value)) 106 | if value is not None and not isinstance(value, client.Credentials): 107 | raise TypeError( 108 | 'Property {0} must be convertible to a credentials ' 109 | 'instance; received: {1}.'.format(self._name, value)) 110 | 111 | def _to_base_type(self, value): 112 | """Converts our validated value to a JSON serialized string. 113 | 114 | Args: 115 | value: A value to be set in the datastore. 116 | 117 | Returns: 118 | A JSON serialized version of the credential, else '' if value 119 | is None. 120 | """ 121 | if value is None: 122 | return '' 123 | else: 124 | return value.to_json() 125 | 126 | def _from_base_type(self, value): 127 | """Converts our stored JSON string back to the desired type. 128 | 129 | Args: 130 | value: A value from the datastore to be converted to the 131 | desired type. 132 | 133 | Returns: 134 | A deserialized Credentials (or subclass) object, else None if 135 | the value can't be parsed. 136 | """ 137 | if not value: 138 | return None 139 | try: 140 | # Uses the from_json method of the implied class of value 141 | credentials = client.Credentials.new_from_json(value) 142 | except ValueError: 143 | credentials = None 144 | return credentials 145 | 146 | 147 | class CredentialsNDBModel(ndb.Model): 148 | """NDB Model for storage of OAuth 2.0 Credentials 149 | 150 | Since this model uses the same kind as CredentialsModel and has a 151 | property which can serialize and deserialize Credentials correctly, it 152 | can be used interchangeably with a CredentialsModel to access, insert 153 | and delete the same entities. This simply provides an NDB model for 154 | interacting with the same data the DB model interacts with. 155 | 156 | Storage of the model is keyed by the user.user_id(). 157 | """ 158 | credentials = CredentialsNDBProperty() 159 | 160 | @classmethod 161 | def _get_kind(cls): 162 | """Return the kind name for this class.""" 163 | return 'CredentialsModel' 164 | -------------------------------------------------------------------------------- /oauth2client/contrib/_metadata.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Provides helper methods for talking to the Compute Engine metadata server. 16 | 17 | See https://cloud.google.com/compute/docs/metadata 18 | """ 19 | 20 | import datetime 21 | import json 22 | import os 23 | 24 | from six.moves import http_client 25 | from six.moves.urllib import parse as urlparse 26 | 27 | from oauth2client import _helpers 28 | from oauth2client import client 29 | from oauth2client import transport 30 | 31 | 32 | METADATA_ROOT = 'http://{}/computeMetadata/v1/'.format( 33 | os.getenv('GCE_METADATA_ROOT', 'metadata.google.internal')) 34 | METADATA_HEADERS = {'Metadata-Flavor': 'Google'} 35 | 36 | 37 | def get(http, path, root=METADATA_ROOT, recursive=None): 38 | """Fetch a resource from the metadata server. 39 | 40 | Args: 41 | http: an object to be used to make HTTP requests. 42 | path: A string indicating the resource to retrieve. For example, 43 | 'instance/service-accounts/default' 44 | root: A string indicating the full path to the metadata server root. 45 | recursive: A boolean indicating whether to do a recursive query of 46 | metadata. See 47 | https://cloud.google.com/compute/docs/metadata#aggcontents 48 | 49 | Returns: 50 | A dictionary if the metadata server returns JSON, otherwise a string. 51 | 52 | Raises: 53 | http_client.HTTPException if an error corrured while 54 | retrieving metadata. 55 | """ 56 | url = urlparse.urljoin(root, path) 57 | url = _helpers._add_query_parameter(url, 'recursive', recursive) 58 | 59 | response, content = transport.request( 60 | http, url, headers=METADATA_HEADERS) 61 | 62 | if response.status == http_client.OK: 63 | decoded = _helpers._from_bytes(content) 64 | if response['content-type'] == 'application/json': 65 | return json.loads(decoded) 66 | else: 67 | return decoded 68 | else: 69 | raise http_client.HTTPException( 70 | 'Failed to retrieve {0} from the Google Compute Engine' 71 | 'metadata service. Response:\n{1}'.format(url, response)) 72 | 73 | 74 | def get_service_account_info(http, service_account='default'): 75 | """Get information about a service account from the metadata server. 76 | 77 | Args: 78 | http: an object to be used to make HTTP requests. 79 | service_account: An email specifying the service account for which to 80 | look up information. Default will be information for the "default" 81 | service account of the current compute engine instance. 82 | 83 | Returns: 84 | A dictionary with information about the specified service account, 85 | for example: 86 | 87 | { 88 | 'email': '...', 89 | 'scopes': ['scope', ...], 90 | 'aliases': ['default', '...'] 91 | } 92 | """ 93 | return get( 94 | http, 95 | 'instance/service-accounts/{0}/'.format(service_account), 96 | recursive=True) 97 | 98 | 99 | def get_token(http, service_account='default'): 100 | """Fetch an oauth token for the 101 | 102 | Args: 103 | http: an object to be used to make HTTP requests. 104 | service_account: An email specifying the service account this token 105 | should represent. Default will be a token for the "default" service 106 | account of the current compute engine instance. 107 | 108 | Returns: 109 | A tuple of (access token, token expiration), where access token is the 110 | access token as a string and token expiration is a datetime object 111 | that indicates when the access token will expire. 112 | """ 113 | token_json = get( 114 | http, 115 | 'instance/service-accounts/{0}/token'.format(service_account)) 116 | token_expiry = client._UTCNOW() + datetime.timedelta( 117 | seconds=token_json['expires_in']) 118 | return token_json['access_token'], token_expiry 119 | -------------------------------------------------------------------------------- /oauth2client/contrib/devshell.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """OAuth 2.0 utitilies for Google Developer Shell environment.""" 16 | 17 | import datetime 18 | import json 19 | import os 20 | import socket 21 | 22 | from oauth2client import _helpers 23 | from oauth2client import client 24 | 25 | DEVSHELL_ENV = 'DEVSHELL_CLIENT_PORT' 26 | 27 | 28 | class Error(Exception): 29 | """Errors for this module.""" 30 | pass 31 | 32 | 33 | class CommunicationError(Error): 34 | """Errors for communication with the Developer Shell server.""" 35 | 36 | 37 | class NoDevshellServer(Error): 38 | """Error when no Developer Shell server can be contacted.""" 39 | 40 | 41 | # The request for credential information to the Developer Shell client socket 42 | # is always an empty PBLite-formatted JSON object, so just define it as a 43 | # constant. 44 | CREDENTIAL_INFO_REQUEST_JSON = '[]' 45 | 46 | 47 | class CredentialInfoResponse(object): 48 | """Credential information response from Developer Shell server. 49 | 50 | The credential information response from Developer Shell socket is a 51 | PBLite-formatted JSON array with fields encoded by their index in the 52 | array: 53 | 54 | * Index 0 - user email 55 | * Index 1 - default project ID. None if the project context is not known. 56 | * Index 2 - OAuth2 access token. None if there is no valid auth context. 57 | * Index 3 - Seconds until the access token expires. None if not present. 58 | """ 59 | 60 | def __init__(self, json_string): 61 | """Initialize the response data from JSON PBLite array.""" 62 | pbl = json.loads(json_string) 63 | if not isinstance(pbl, list): 64 | raise ValueError('Not a list: ' + str(pbl)) 65 | pbl_len = len(pbl) 66 | self.user_email = pbl[0] if pbl_len > 0 else None 67 | self.project_id = pbl[1] if pbl_len > 1 else None 68 | self.access_token = pbl[2] if pbl_len > 2 else None 69 | self.expires_in = pbl[3] if pbl_len > 3 else None 70 | 71 | 72 | def _SendRecv(): 73 | """Communicate with the Developer Shell server socket.""" 74 | 75 | port = int(os.getenv(DEVSHELL_ENV, 0)) 76 | if port == 0: 77 | raise NoDevshellServer() 78 | 79 | sock = socket.socket() 80 | sock.connect(('localhost', port)) 81 | 82 | data = CREDENTIAL_INFO_REQUEST_JSON 83 | msg = '{0}\n{1}'.format(len(data), data) 84 | sock.sendall(_helpers._to_bytes(msg, encoding='utf-8')) 85 | 86 | header = sock.recv(6).decode() 87 | if '\n' not in header: 88 | raise CommunicationError('saw no newline in the first 6 bytes') 89 | len_str, json_str = header.split('\n', 1) 90 | to_read = int(len_str) - len(json_str) 91 | if to_read > 0: 92 | json_str += sock.recv(to_read, socket.MSG_WAITALL).decode() 93 | 94 | return CredentialInfoResponse(json_str) 95 | 96 | 97 | class DevshellCredentials(client.GoogleCredentials): 98 | """Credentials object for Google Developer Shell environment. 99 | 100 | This object will allow a Google Developer Shell session to identify its 101 | user to Google and other OAuth 2.0 servers that can verify assertions. It 102 | can be used for the purpose of accessing data stored under the user 103 | account. 104 | 105 | This credential does not require a flow to instantiate because it 106 | represents a two legged flow, and therefore has all of the required 107 | information to generate and refresh its own access tokens. 108 | """ 109 | 110 | def __init__(self, user_agent=None): 111 | super(DevshellCredentials, self).__init__( 112 | None, # access_token, initialized below 113 | None, # client_id 114 | None, # client_secret 115 | None, # refresh_token 116 | None, # token_expiry 117 | None, # token_uri 118 | user_agent) 119 | self._refresh(None) 120 | 121 | def _refresh(self, http): 122 | """Refreshes the access token. 123 | 124 | Args: 125 | http: unused HTTP object 126 | """ 127 | self.devshell_response = _SendRecv() 128 | self.access_token = self.devshell_response.access_token 129 | expires_in = self.devshell_response.expires_in 130 | if expires_in is not None: 131 | delta = datetime.timedelta(seconds=expires_in) 132 | self.token_expiry = client._UTCNOW() + delta 133 | else: 134 | self.token_expiry = None 135 | 136 | @property 137 | def user_email(self): 138 | return self.devshell_response.user_email 139 | 140 | @property 141 | def project_id(self): 142 | return self.devshell_response.project_id 143 | 144 | @classmethod 145 | def from_json(cls, json_data): 146 | raise NotImplementedError( 147 | 'Cannot load Developer Shell credentials from JSON.') 148 | 149 | @property 150 | def serialization_data(self): 151 | raise NotImplementedError( 152 | 'Cannot serialize Developer Shell credentials.') 153 | -------------------------------------------------------------------------------- /oauth2client/contrib/dictionary_storage.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Dictionary storage for OAuth2 Credentials.""" 16 | 17 | from oauth2client import client 18 | 19 | 20 | class DictionaryStorage(client.Storage): 21 | """Store and retrieve credentials to and from a dictionary-like object. 22 | 23 | Args: 24 | dictionary: A dictionary or dictionary-like object. 25 | key: A string or other hashable. The credentials will be stored in 26 | ``dictionary[key]``. 27 | lock: An optional threading.Lock-like object. The lock will be 28 | acquired before anything is written or read from the 29 | dictionary. 30 | """ 31 | 32 | def __init__(self, dictionary, key, lock=None): 33 | """Construct a DictionaryStorage instance.""" 34 | super(DictionaryStorage, self).__init__(lock=lock) 35 | self._dictionary = dictionary 36 | self._key = key 37 | 38 | def locked_get(self): 39 | """Retrieve the credentials from the dictionary, if they exist. 40 | 41 | Returns: A :class:`oauth2client.client.OAuth2Credentials` instance. 42 | """ 43 | serialized = self._dictionary.get(self._key) 44 | 45 | if serialized is None: 46 | return None 47 | 48 | credentials = client.OAuth2Credentials.from_json(serialized) 49 | credentials.set_store(self) 50 | 51 | return credentials 52 | 53 | def locked_put(self, credentials): 54 | """Save the credentials to the dictionary. 55 | 56 | Args: 57 | credentials: A :class:`oauth2client.client.OAuth2Credentials` 58 | instance. 59 | """ 60 | serialized = credentials.to_json() 61 | self._dictionary[self._key] = serialized 62 | 63 | def locked_delete(self): 64 | """Remove the credentials from the dictionary, if they exist.""" 65 | self._dictionary.pop(self._key, None) 66 | -------------------------------------------------------------------------------- /oauth2client/contrib/django_util/apps.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Application Config For Django OAuth2 Helper. 16 | 17 | Django 1.7+ provides an 18 | [applications](https://docs.djangoproject.com/en/1.8/ref/applications/) 19 | API so that Django projects can introspect on installed applications using a 20 | stable API. This module exists to follow that convention. 21 | """ 22 | 23 | import sys 24 | 25 | # Django 1.7+ only supports Python 2.7+ 26 | if sys.hexversion >= 0x02070000: # pragma: NO COVER 27 | from django.apps import AppConfig 28 | 29 | class GoogleOAuth2HelperConfig(AppConfig): 30 | """ App Config for Django Helper""" 31 | name = 'oauth2client.django_util' 32 | verbose_name = "Google OAuth2 Django Helper" 33 | -------------------------------------------------------------------------------- /oauth2client/contrib/django_util/decorators.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Decorators for Django OAuth2 Flow. 16 | 17 | Contains two decorators, ``oauth_required`` and ``oauth_enabled``. 18 | 19 | ``oauth_required`` will ensure that a user has an oauth object containing 20 | credentials associated with the request, and if not, redirect to the 21 | authorization flow. 22 | 23 | ``oauth_enabled`` will attach the oauth2 object containing credentials if it 24 | exists. If it doesn't, the view will still render, but helper methods will be 25 | attached to start the oauth2 flow. 26 | """ 27 | 28 | from django import shortcuts 29 | import django.conf 30 | from six import wraps 31 | from six.moves.urllib import parse 32 | 33 | from oauth2client.contrib import django_util 34 | 35 | 36 | def oauth_required(decorated_function=None, scopes=None, **decorator_kwargs): 37 | """ Decorator to require OAuth2 credentials for a view. 38 | 39 | 40 | .. code-block:: python 41 | :caption: views.py 42 | :name: views_required_2 43 | 44 | 45 | from oauth2client.django_util.decorators import oauth_required 46 | 47 | @oauth_required 48 | def requires_default_scopes(request): 49 | email = request.credentials.id_token['email'] 50 | service = build(serviceName='calendar', version='v3', 51 | http=request.oauth.http, 52 | developerKey=API_KEY) 53 | events = service.events().list( 54 | calendarId='primary').execute()['items'] 55 | return HttpResponse( 56 | "email: {0}, calendar: {1}".format(email, str(events))) 57 | 58 | Args: 59 | decorated_function: View function to decorate, must have the Django 60 | request object as the first argument. 61 | scopes: Scopes to require, will default. 62 | decorator_kwargs: Can include ``return_url`` to specify the URL to 63 | return to after OAuth2 authorization is complete. 64 | 65 | Returns: 66 | An OAuth2 Authorize view if credentials are not found or if the 67 | credentials are missing the required scopes. Otherwise, 68 | the decorated view. 69 | """ 70 | def curry_wrapper(wrapped_function): 71 | @wraps(wrapped_function) 72 | def required_wrapper(request, *args, **kwargs): 73 | if not (django_util.oauth2_settings.storage_model is None or 74 | request.user.is_authenticated()): 75 | redirect_str = '{0}?next={1}'.format( 76 | django.conf.settings.LOGIN_URL, 77 | parse.quote(request.path)) 78 | return shortcuts.redirect(redirect_str) 79 | 80 | return_url = decorator_kwargs.pop('return_url', 81 | request.get_full_path()) 82 | user_oauth = django_util.UserOAuth2(request, scopes, return_url) 83 | if not user_oauth.has_credentials(): 84 | return shortcuts.redirect(user_oauth.get_authorize_redirect()) 85 | setattr(request, django_util.oauth2_settings.request_prefix, 86 | user_oauth) 87 | return wrapped_function(request, *args, **kwargs) 88 | 89 | return required_wrapper 90 | 91 | if decorated_function: 92 | return curry_wrapper(decorated_function) 93 | else: 94 | return curry_wrapper 95 | 96 | 97 | def oauth_enabled(decorated_function=None, scopes=None, **decorator_kwargs): 98 | """ Decorator to enable OAuth Credentials if authorized, and setup 99 | the oauth object on the request object to provide helper functions 100 | to start the flow otherwise. 101 | 102 | .. code-block:: python 103 | :caption: views.py 104 | :name: views_enabled3 105 | 106 | from oauth2client.django_util.decorators import oauth_enabled 107 | 108 | @oauth_enabled 109 | def optional_oauth2(request): 110 | if request.oauth.has_credentials(): 111 | # this could be passed into a view 112 | # request.oauth.http is also initialized 113 | return HttpResponse("User email: {0}".format( 114 | request.oauth.credentials.id_token['email']) 115 | else: 116 | return HttpResponse('Here is an OAuth Authorize link: 117 | Authorize'.format( 118 | request.oauth.get_authorize_redirect())) 119 | 120 | 121 | Args: 122 | decorated_function: View function to decorate. 123 | scopes: Scopes to require, will default. 124 | decorator_kwargs: Can include ``return_url`` to specify the URL to 125 | return to after OAuth2 authorization is complete. 126 | 127 | Returns: 128 | The decorated view function. 129 | """ 130 | def curry_wrapper(wrapped_function): 131 | @wraps(wrapped_function) 132 | def enabled_wrapper(request, *args, **kwargs): 133 | return_url = decorator_kwargs.pop('return_url', 134 | request.get_full_path()) 135 | user_oauth = django_util.UserOAuth2(request, scopes, return_url) 136 | setattr(request, django_util.oauth2_settings.request_prefix, 137 | user_oauth) 138 | return wrapped_function(request, *args, **kwargs) 139 | 140 | return enabled_wrapper 141 | 142 | if decorated_function: 143 | return curry_wrapper(decorated_function) 144 | else: 145 | return curry_wrapper 146 | -------------------------------------------------------------------------------- /oauth2client/contrib/django_util/models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Contains classes used for the Django ORM storage.""" 16 | 17 | import base64 18 | import pickle 19 | 20 | from django.db import models 21 | from django.utils import encoding 22 | import jsonpickle 23 | 24 | import oauth2client 25 | 26 | 27 | class CredentialsField(models.Field): 28 | """Django ORM field for storing OAuth2 Credentials.""" 29 | 30 | def __init__(self, *args, **kwargs): 31 | if 'null' not in kwargs: 32 | kwargs['null'] = True 33 | super(CredentialsField, self).__init__(*args, **kwargs) 34 | 35 | def get_internal_type(self): 36 | return 'BinaryField' 37 | 38 | def from_db_value(self, value, expression, connection, context): 39 | """Overrides ``models.Field`` method. This converts the value 40 | returned from the database to an instance of this class. 41 | """ 42 | return self.to_python(value) 43 | 44 | def to_python(self, value): 45 | """Overrides ``models.Field`` method. This is used to convert 46 | bytes (from serialization etc) to an instance of this class""" 47 | if value is None: 48 | return None 49 | elif isinstance(value, oauth2client.client.Credentials): 50 | return value 51 | else: 52 | try: 53 | return jsonpickle.decode( 54 | base64.b64decode(encoding.smart_bytes(value)).decode()) 55 | except ValueError: 56 | return pickle.loads( 57 | base64.b64decode(encoding.smart_bytes(value))) 58 | 59 | def get_prep_value(self, value): 60 | """Overrides ``models.Field`` method. This is used to convert 61 | the value from an instances of this class to bytes that can be 62 | inserted into the database. 63 | """ 64 | if value is None: 65 | return None 66 | else: 67 | return encoding.smart_text( 68 | base64.b64encode(jsonpickle.encode(value).encode())) 69 | 70 | def value_to_string(self, obj): 71 | """Convert the field value from the provided model to a string. 72 | 73 | Used during model serialization. 74 | 75 | Args: 76 | obj: db.Model, model object 77 | 78 | Returns: 79 | string, the serialized field value 80 | """ 81 | value = self._get_val_from_obj(obj) 82 | return self.get_prep_value(value) 83 | -------------------------------------------------------------------------------- /oauth2client/contrib/django_util/signals.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Signals for Google OAuth2 Helper. 16 | 17 | This module contains signals for Google OAuth2 Helper. Currently it only 18 | contains one, which fires when an OAuth2 authorization flow has completed. 19 | """ 20 | 21 | import django.dispatch 22 | 23 | """Signal that fires when OAuth2 Flow has completed. 24 | It passes the Django request object and the OAuth2 credentials object to the 25 | receiver. 26 | """ 27 | oauth2_authorized = django.dispatch.Signal( 28 | providing_args=["request", "credentials"]) 29 | -------------------------------------------------------------------------------- /oauth2client/contrib/django_util/site.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Contains Django URL patterns used for OAuth2 flow.""" 16 | 17 | from django.conf import urls 18 | 19 | from oauth2client.contrib.django_util import views 20 | 21 | urlpatterns = [ 22 | urls.url(r'oauth2callback/', views.oauth2_callback, name="callback"), 23 | urls.url(r'oauth2authorize/', views.oauth2_authorize, name="authorize") 24 | ] 25 | 26 | urls = (urlpatterns, "google_oauth", "google_oauth") 27 | -------------------------------------------------------------------------------- /oauth2client/contrib/django_util/storage.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Contains a storage module that stores credentials using the Django ORM.""" 16 | 17 | from oauth2client import client 18 | 19 | 20 | class DjangoORMStorage(client.Storage): 21 | """Store and retrieve a single credential to and from the Django datastore. 22 | 23 | This Storage helper presumes the Credentials 24 | have been stored as a CredentialsField 25 | on a db model class. 26 | """ 27 | 28 | def __init__(self, model_class, key_name, key_value, property_name): 29 | """Constructor for Storage. 30 | 31 | Args: 32 | model: string, fully qualified name of db.Model model class. 33 | key_name: string, key name for the entity that has the credentials 34 | key_value: string, key value for the entity that has the 35 | credentials. 36 | property_name: string, name of the property that is an 37 | CredentialsProperty. 38 | """ 39 | super(DjangoORMStorage, self).__init__() 40 | self.model_class = model_class 41 | self.key_name = key_name 42 | self.key_value = key_value 43 | self.property_name = property_name 44 | 45 | def locked_get(self): 46 | """Retrieve stored credential from the Django ORM. 47 | 48 | Returns: 49 | oauth2client.Credentials retrieved from the Django ORM, associated 50 | with the ``model``, ``key_value``->``key_name`` pair used to query 51 | for the model, and ``property_name`` identifying the 52 | ``CredentialsProperty`` field, all of which are defined in the 53 | constructor for this Storage object. 54 | 55 | """ 56 | query = {self.key_name: self.key_value} 57 | entities = self.model_class.objects.filter(**query) 58 | if len(entities) > 0: 59 | credential = getattr(entities[0], self.property_name) 60 | if getattr(credential, 'set_store', None) is not None: 61 | credential.set_store(self) 62 | return credential 63 | else: 64 | return None 65 | 66 | def locked_put(self, credentials): 67 | """Write a Credentials to the Django datastore. 68 | 69 | Args: 70 | credentials: Credentials, the credentials to store. 71 | """ 72 | entity, _ = self.model_class.objects.get_or_create( 73 | **{self.key_name: self.key_value}) 74 | 75 | setattr(entity, self.property_name, credentials) 76 | entity.save() 77 | 78 | def locked_delete(self): 79 | """Delete Credentials from the datastore.""" 80 | query = {self.key_name: self.key_value} 81 | self.model_class.objects.filter(**query).delete() 82 | -------------------------------------------------------------------------------- /oauth2client/contrib/gce.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utilities for Google Compute Engine 16 | 17 | Utilities for making it easier to use OAuth 2.0 on Google Compute Engine. 18 | """ 19 | 20 | import logging 21 | import warnings 22 | 23 | from six.moves import http_client 24 | 25 | from oauth2client import client 26 | from oauth2client.contrib import _metadata 27 | 28 | 29 | logger = logging.getLogger(__name__) 30 | 31 | _SCOPES_WARNING = """\ 32 | You have requested explicit scopes to be used with a GCE service account. 33 | Using this argument will have no effect on the actual scopes for tokens 34 | requested. These scopes are set at VM instance creation time and 35 | can't be overridden in the request. 36 | """ 37 | 38 | 39 | class AppAssertionCredentials(client.AssertionCredentials): 40 | """Credentials object for Compute Engine Assertion Grants 41 | 42 | This object will allow a Compute Engine instance to identify itself to 43 | Google and other OAuth 2.0 servers that can verify assertions. It can be 44 | used for the purpose of accessing data stored under an account assigned to 45 | the Compute Engine instance itself. 46 | 47 | This credential does not require a flow to instantiate because it 48 | represents a two legged flow, and therefore has all of the required 49 | information to generate and refresh its own access tokens. 50 | 51 | Note that :attr:`service_account_email` and :attr:`scopes` 52 | will both return None until the credentials have been refreshed. 53 | To check whether credentials have previously been refreshed use 54 | :attr:`invalid`. 55 | """ 56 | 57 | def __init__(self, email=None, *args, **kwargs): 58 | """Constructor for AppAssertionCredentials 59 | 60 | Args: 61 | email: an email that specifies the service account to use. 62 | Only necessary if using custom service accounts 63 | (see https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances#createdefaultserviceaccount). 64 | """ 65 | if 'scopes' in kwargs: 66 | warnings.warn(_SCOPES_WARNING) 67 | kwargs['scopes'] = None 68 | 69 | # Assertion type is no longer used, but still in the 70 | # parent class signature. 71 | super(AppAssertionCredentials, self).__init__(None, *args, **kwargs) 72 | 73 | self.service_account_email = email 74 | self.scopes = None 75 | self.invalid = True 76 | 77 | @classmethod 78 | def from_json(cls, json_data): 79 | raise NotImplementedError( 80 | 'Cannot serialize credentials for GCE service accounts.') 81 | 82 | def to_json(self): 83 | raise NotImplementedError( 84 | 'Cannot serialize credentials for GCE service accounts.') 85 | 86 | def retrieve_scopes(self, http): 87 | """Retrieves the canonical list of scopes for this access token. 88 | 89 | Overrides client.Credentials.retrieve_scopes. Fetches scopes info 90 | from the metadata server. 91 | 92 | Args: 93 | http: httplib2.Http, an http object to be used to make the refresh 94 | request. 95 | 96 | Returns: 97 | A set of strings containing the canonical list of scopes. 98 | """ 99 | self._retrieve_info(http) 100 | return self.scopes 101 | 102 | def _retrieve_info(self, http): 103 | """Retrieves service account info for invalid credentials. 104 | 105 | Args: 106 | http: an object to be used to make HTTP requests. 107 | """ 108 | if self.invalid: 109 | info = _metadata.get_service_account_info( 110 | http, 111 | service_account=self.service_account_email or 'default') 112 | self.invalid = False 113 | self.service_account_email = info['email'] 114 | self.scopes = info['scopes'] 115 | 116 | def _refresh(self, http): 117 | """Refreshes the access token. 118 | 119 | Skip all the storage hoops and just refresh using the API. 120 | 121 | Args: 122 | http: an object to be used to make HTTP requests. 123 | 124 | Raises: 125 | HttpAccessTokenRefreshError: When the refresh fails. 126 | """ 127 | try: 128 | self._retrieve_info(http) 129 | self.access_token, self.token_expiry = _metadata.get_token( 130 | http, service_account=self.service_account_email) 131 | except http_client.HTTPException as err: 132 | raise client.HttpAccessTokenRefreshError(str(err)) 133 | 134 | @property 135 | def serialization_data(self): 136 | raise NotImplementedError( 137 | 'Cannot serialize credentials for GCE service accounts.') 138 | 139 | def create_scoped_required(self): 140 | return False 141 | 142 | def sign_blob(self, blob): 143 | """Cryptographically sign a blob (of bytes). 144 | 145 | This method is provided to support a common interface, but 146 | the actual key used for a Google Compute Engine service account 147 | is not available, so it can't be used to sign content. 148 | 149 | Args: 150 | blob: bytes, Message to be signed. 151 | 152 | Raises: 153 | NotImplementedError, always. 154 | """ 155 | raise NotImplementedError( 156 | 'Compute Engine service accounts cannot sign blobs') 157 | -------------------------------------------------------------------------------- /oauth2client/contrib/keyring_storage.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """A keyring based Storage. 16 | 17 | A Storage for Credentials that uses the keyring module. 18 | """ 19 | 20 | import threading 21 | 22 | import keyring 23 | 24 | from oauth2client import client 25 | 26 | 27 | class Storage(client.Storage): 28 | """Store and retrieve a single credential to and from the keyring. 29 | 30 | To use this module you must have the keyring module installed. See 31 | . This is an optional module and is 32 | not installed with oauth2client by default because it does not work on all 33 | the platforms that oauth2client supports, such as Google App Engine. 34 | 35 | The keyring module is a 36 | cross-platform library for access the keyring capabilities of the local 37 | system. The user will be prompted for their keyring password when this 38 | module is used, and the manner in which the user is prompted will vary per 39 | platform. 40 | 41 | Usage:: 42 | 43 | from oauth2client import keyring_storage 44 | 45 | s = keyring_storage.Storage('name_of_application', 'user1') 46 | credentials = s.get() 47 | 48 | """ 49 | 50 | def __init__(self, service_name, user_name): 51 | """Constructor. 52 | 53 | Args: 54 | service_name: string, The name of the service under which the 55 | credentials are stored. 56 | user_name: string, The name of the user to store credentials for. 57 | """ 58 | super(Storage, self).__init__(lock=threading.Lock()) 59 | self._service_name = service_name 60 | self._user_name = user_name 61 | 62 | def locked_get(self): 63 | """Retrieve Credential from file. 64 | 65 | Returns: 66 | oauth2client.client.Credentials 67 | """ 68 | credentials = None 69 | content = keyring.get_password(self._service_name, self._user_name) 70 | 71 | if content is not None: 72 | try: 73 | credentials = client.Credentials.new_from_json(content) 74 | credentials.set_store(self) 75 | except ValueError: 76 | pass 77 | 78 | return credentials 79 | 80 | def locked_put(self, credentials): 81 | """Write Credentials to file. 82 | 83 | Args: 84 | credentials: Credentials, the credentials to store. 85 | """ 86 | keyring.set_password(self._service_name, self._user_name, 87 | credentials.to_json()) 88 | 89 | def locked_delete(self): 90 | """Delete Credentials file. 91 | 92 | Args: 93 | credentials: Credentials, the credentials to store. 94 | """ 95 | keyring.set_password(self._service_name, self._user_name, '') 96 | -------------------------------------------------------------------------------- /oauth2client/contrib/sqlalchemy.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """OAuth 2.0 utilities for SQLAlchemy. 16 | 17 | Utilities for using OAuth 2.0 in conjunction with a SQLAlchemy. 18 | 19 | Configuration 20 | ============= 21 | 22 | In order to use this storage, you'll need to create table 23 | with :class:`oauth2client.contrib.sqlalchemy.CredentialsType` column. 24 | It's recommended to either put this column on some sort of user info 25 | table or put the column in a table with a belongs-to relationship to 26 | a user info table. 27 | 28 | Here's an example of a simple table with a :class:`CredentialsType` 29 | column that's related to a user table by the `user_id` key. 30 | 31 | .. code-block:: python 32 | 33 | from sqlalchemy import Column, ForeignKey, Integer 34 | from sqlalchemy.ext.declarative import declarative_base 35 | from sqlalchemy.orm import relationship 36 | 37 | from oauth2client.contrib.sqlalchemy import CredentialsType 38 | 39 | 40 | Base = declarative_base() 41 | 42 | 43 | class Credentials(Base): 44 | __tablename__ = 'credentials' 45 | 46 | user_id = Column(Integer, ForeignKey('user.id')) 47 | credentials = Column(CredentialsType) 48 | 49 | 50 | class User(Base): 51 | id = Column(Integer, primary_key=True) 52 | # bunch of other columns 53 | credentials = relationship('Credentials') 54 | 55 | 56 | Usage 57 | ===== 58 | 59 | With tables ready, you are now able to store credentials in database. 60 | We will reuse tables defined above. 61 | 62 | .. code-block:: python 63 | 64 | from sqlalchemy.orm import Session 65 | 66 | from oauth2client.client import OAuth2Credentials 67 | from oauth2client.contrib.sql_alchemy import Storage 68 | 69 | session = Session() 70 | user = session.query(User).first() 71 | storage = Storage( 72 | session=session, 73 | model_class=Credentials, 74 | # This is the key column used to identify 75 | # the row that stores the credentials. 76 | key_name='user_id', 77 | key_value=user.id, 78 | property_name='credentials', 79 | ) 80 | 81 | # Store 82 | credentials = OAuth2Credentials(...) 83 | storage.put(credentials) 84 | 85 | # Retrieve 86 | credentials = storage.get() 87 | 88 | # Delete 89 | storage.delete() 90 | 91 | """ 92 | 93 | from __future__ import absolute_import 94 | 95 | import sqlalchemy.types 96 | 97 | from oauth2client import client 98 | 99 | 100 | class CredentialsType(sqlalchemy.types.PickleType): 101 | """Type representing credentials. 102 | 103 | Alias for :class:`sqlalchemy.types.PickleType`. 104 | """ 105 | 106 | 107 | class Storage(client.Storage): 108 | """Store and retrieve a single credential to and from SQLAlchemy. 109 | This helper presumes the Credentials 110 | have been stored as a Credentials column 111 | on a db model class. 112 | """ 113 | 114 | def __init__(self, session, model_class, key_name, 115 | key_value, property_name): 116 | """Constructor for Storage. 117 | 118 | Args: 119 | session: An instance of :class:`sqlalchemy.orm.Session`. 120 | model_class: SQLAlchemy declarative mapping. 121 | key_name: string, key name for the entity that has the credentials 122 | key_value: key value for the entity that has the credentials 123 | property_name: A string indicating which property on the 124 | ``model_class`` to store the credentials. 125 | This property must be a 126 | :class:`CredentialsType` column. 127 | """ 128 | super(Storage, self).__init__() 129 | 130 | self.session = session 131 | self.model_class = model_class 132 | self.key_name = key_name 133 | self.key_value = key_value 134 | self.property_name = property_name 135 | 136 | def locked_get(self): 137 | """Retrieve stored credential. 138 | 139 | Returns: 140 | A :class:`oauth2client.Credentials` instance or `None`. 141 | """ 142 | filters = {self.key_name: self.key_value} 143 | query = self.session.query(self.model_class).filter_by(**filters) 144 | entity = query.first() 145 | 146 | if entity: 147 | credential = getattr(entity, self.property_name) 148 | if credential and hasattr(credential, 'set_store'): 149 | credential.set_store(self) 150 | return credential 151 | else: 152 | return None 153 | 154 | def locked_put(self, credentials): 155 | """Write a credentials to the SQLAlchemy datastore. 156 | 157 | Args: 158 | credentials: :class:`oauth2client.Credentials` 159 | """ 160 | filters = {self.key_name: self.key_value} 161 | query = self.session.query(self.model_class).filter_by(**filters) 162 | entity = query.first() 163 | 164 | if not entity: 165 | entity = self.model_class(**filters) 166 | 167 | setattr(entity, self.property_name, credentials) 168 | self.session.add(entity) 169 | 170 | def locked_delete(self): 171 | """Delete credentials from the SQLAlchemy datastore.""" 172 | filters = {self.key_name: self.key_value} 173 | self.session.query(self.model_class).filter_by(**filters).delete() 174 | -------------------------------------------------------------------------------- /oauth2client/contrib/xsrfutil.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 the Melange authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Helper methods for creating & verifying XSRF tokens.""" 16 | 17 | import base64 18 | import binascii 19 | import hmac 20 | import time 21 | 22 | from oauth2client import _helpers 23 | 24 | 25 | # Delimiter character 26 | DELIMITER = b':' 27 | 28 | # 1 hour in seconds 29 | DEFAULT_TIMEOUT_SECS = 60 * 60 30 | 31 | 32 | @_helpers.positional(2) 33 | def generate_token(key, user_id, action_id='', when=None): 34 | """Generates a URL-safe token for the given user, action, time tuple. 35 | 36 | Args: 37 | key: secret key to use. 38 | user_id: the user ID of the authenticated user. 39 | action_id: a string identifier of the action they requested 40 | authorization for. 41 | when: the time in seconds since the epoch at which the user was 42 | authorized for this action. If not set the current time is used. 43 | 44 | Returns: 45 | A string XSRF protection token. 46 | """ 47 | digester = hmac.new(_helpers._to_bytes(key, encoding='utf-8')) 48 | digester.update(_helpers._to_bytes(str(user_id), encoding='utf-8')) 49 | digester.update(DELIMITER) 50 | digester.update(_helpers._to_bytes(action_id, encoding='utf-8')) 51 | digester.update(DELIMITER) 52 | when = _helpers._to_bytes(str(when or int(time.time())), encoding='utf-8') 53 | digester.update(when) 54 | digest = digester.digest() 55 | 56 | token = base64.urlsafe_b64encode(digest + DELIMITER + when) 57 | return token 58 | 59 | 60 | @_helpers.positional(3) 61 | def validate_token(key, token, user_id, action_id="", current_time=None): 62 | """Validates that the given token authorizes the user for the action. 63 | 64 | Tokens are invalid if the time of issue is too old or if the token 65 | does not match what generateToken outputs (i.e. the token was forged). 66 | 67 | Args: 68 | key: secret key to use. 69 | token: a string of the token generated by generateToken. 70 | user_id: the user ID of the authenticated user. 71 | action_id: a string identifier of the action they requested 72 | authorization for. 73 | 74 | Returns: 75 | A boolean - True if the user is authorized for the action, False 76 | otherwise. 77 | """ 78 | if not token: 79 | return False 80 | try: 81 | decoded = base64.urlsafe_b64decode(token) 82 | token_time = int(decoded.split(DELIMITER)[-1]) 83 | except (TypeError, ValueError, binascii.Error): 84 | return False 85 | if current_time is None: 86 | current_time = time.time() 87 | # If the token is too old it's not valid. 88 | if current_time - token_time > DEFAULT_TIMEOUT_SECS: 89 | return False 90 | 91 | # The given token should match the generated one with the same time. 92 | expected_token = generate_token(key, user_id, action_id=action_id, 93 | when=token_time) 94 | if len(token) != len(expected_token): 95 | return False 96 | 97 | # Perform constant time comparison to avoid timing attacks 98 | different = 0 99 | for x, y in zip(bytearray(token), bytearray(expected_token)): 100 | different |= x ^ y 101 | return not different 102 | -------------------------------------------------------------------------------- /oauth2client/file.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utilities for OAuth. 16 | 17 | Utilities for making it easier to work with OAuth 2.0 18 | credentials. 19 | """ 20 | 21 | import os 22 | import threading 23 | 24 | from oauth2client import _helpers 25 | from oauth2client import client 26 | 27 | 28 | class Storage(client.Storage): 29 | """Store and retrieve a single credential to and from a file.""" 30 | 31 | def __init__(self, filename): 32 | super(Storage, self).__init__(lock=threading.Lock()) 33 | self._filename = filename 34 | 35 | def locked_get(self): 36 | """Retrieve Credential from file. 37 | 38 | Returns: 39 | oauth2client.client.Credentials 40 | 41 | Raises: 42 | IOError if the file is a symbolic link. 43 | """ 44 | credentials = None 45 | _helpers.validate_file(self._filename) 46 | try: 47 | f = open(self._filename, 'rb') 48 | content = f.read() 49 | f.close() 50 | except IOError: 51 | return credentials 52 | 53 | try: 54 | credentials = client.Credentials.new_from_json(content) 55 | credentials.set_store(self) 56 | except ValueError: 57 | pass 58 | 59 | return credentials 60 | 61 | def _create_file_if_needed(self): 62 | """Create an empty file if necessary. 63 | 64 | This method will not initialize the file. Instead it implements a 65 | simple version of "touch" to ensure the file has been created. 66 | """ 67 | if not os.path.exists(self._filename): 68 | old_umask = os.umask(0o177) 69 | try: 70 | open(self._filename, 'a+b').close() 71 | finally: 72 | os.umask(old_umask) 73 | 74 | def locked_put(self, credentials): 75 | """Write Credentials to file. 76 | 77 | Args: 78 | credentials: Credentials, the credentials to store. 79 | 80 | Raises: 81 | IOError if the file is a symbolic link. 82 | """ 83 | self._create_file_if_needed() 84 | _helpers.validate_file(self._filename) 85 | f = open(self._filename, 'w') 86 | f.write(credentials.to_json()) 87 | f.close() 88 | 89 | def locked_delete(self): 90 | """Delete Credentials file. 91 | 92 | Args: 93 | credentials: Credentials, the credentials to store. 94 | """ 95 | os.unlink(self._filename) 96 | -------------------------------------------------------------------------------- /samples/call_compute_service.py: -------------------------------------------------------------------------------- 1 | # To be used to test GoogleCredentials.get_application_default() 2 | # from local machine and GCE. 3 | # The GCE virtual machine needs to have both service account and 4 | # Compute API enabled. 5 | # See: https://developers.google.com/compute/docs/authentication 6 | 7 | from googleapiclient.discovery import build 8 | 9 | from oauth2client.client import GoogleCredentials 10 | 11 | 12 | PROJECT = 'bamboo-machine-422' # Provide your own GCE project here 13 | ZONE = 'us-central1-a' # Put here a zone which has some VMs 14 | 15 | 16 | credentials = GoogleCredentials.get_application_default() 17 | service = build('compute', 'v1', credentials=credentials) 18 | 19 | request = service.instances().list(project=PROJECT, zone=ZONE) 20 | response = request.execute() 21 | 22 | print(response) 23 | -------------------------------------------------------------------------------- /samples/django/README.md: -------------------------------------------------------------------------------- 1 | # Django Samples 2 | 3 | These two sample Django apps provide a skeleton for the two main use cases of the 4 | `oauth2client.contrib.django_util` helpers. 5 | 6 | Please see the 7 | [core docs](https://oauth2client.readthedocs.io/en/latest/) for more information and usage examples. 8 | 9 | ## google_user 10 | 11 | This is the simpler use case of the library. It assumes you are using Google OAuth as your primary 12 | authorization and authentication mechanism for your application. Users log in with their Google ID 13 | and their OAuth2 credentials are stored inside the session. 14 | 15 | ## django_user 16 | 17 | This is the use case where the application is already using the Django authorization system and 18 | has a Django model with a `django.contrib.auth.models.User` field, and would like to attach 19 | a Google OAuth2 credentials object to that model. Users have to login, and then can login with 20 | their Google account to associate the Google account with the user in the Django system. 21 | Credentials will be stored in the Django ORM backend. 22 | -------------------------------------------------------------------------------- /samples/django/django_user/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2016 Google Inc. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | import os 16 | import sys 17 | 18 | if __name__ == "__main__": 19 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myoauth.settings") 20 | 21 | from django.core.management import execute_from_command_line 22 | 23 | execute_from_command_line(sys.argv) 24 | -------------------------------------------------------------------------------- /samples/django/django_user/myoauth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/oauth2client/50d20532a748f18e53f7d24ccbe6647132c979a9/samples/django/django_user/myoauth/__init__.py -------------------------------------------------------------------------------- /samples/django/django_user/myoauth/settings.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | import os 17 | 18 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = 'eiw+mvmua#98n@p2xq+c#liz@r2&#-s07nkgz)+$zcl^o4$-$o' 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = [] 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = ( 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'polls', 41 | 'oauth2client.contrib.django_util', 42 | ) 43 | 44 | MIDDLEWARE_CLASSES = ( 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | 'django.middleware.security.SecurityMiddleware', 52 | ) 53 | 54 | ROOT_URLCONF = 'myoauth.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'myoauth.wsgi.application' 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 81 | } 82 | } 83 | 84 | # Internationalization 85 | # https://docs.djangoproject.com/en/1.8/topics/i18n/ 86 | 87 | LANGUAGE_CODE = 'en-us' 88 | 89 | TIME_ZONE = 'UTC' 90 | 91 | USE_I18N = True 92 | 93 | USE_L10N = True 94 | 95 | USE_TZ = True 96 | 97 | # Static files (CSS, JavaScript, Images) 98 | # https://docs.djangoproject.com/en/1.8/howto/static-files/ 99 | 100 | STATIC_URL = '/static/' 101 | 102 | GOOGLE_OAUTH2_CLIENT_ID = 'YOUR_CLIENT_ID' 103 | 104 | GOOGLE_OAUTH2_CLIENT_SECRET = 'YOUR_CLIENT_SECRET' 105 | 106 | GOOGLE_OAUTH2_SCOPES = ( 107 | 'email', 'profile') 108 | 109 | GOOGLE_OAUTH2_STORAGE_MODEL = { 110 | 'model': 'polls.models.CredentialsModel', 111 | 'user_property': 'user_id', 112 | 'credentials_property': 'credential', 113 | } 114 | 115 | LOGIN_URL = '/login' 116 | -------------------------------------------------------------------------------- /samples/django/django_user/myoauth/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from django.conf import urls 16 | from django.contrib import admin 17 | import django.contrib.auth.views 18 | from polls import views 19 | 20 | import oauth2client.contrib.django_util.site as django_util_site 21 | 22 | 23 | urlpatterns = [ 24 | urls.url(r'^$', views.index), 25 | urls.url(r'^profile_required$', views.get_profile_required), 26 | urls.url(r'^profile_enabled$', views.get_profile_optional), 27 | urls.url(r'^admin/', urls.include(admin.site.urls)), 28 | urls.url(r'^login', django.contrib.auth.views.login, name="login"), 29 | urls.url(r'^oauth2/', urls.include(django_util_site.urls)), 30 | ] 31 | -------------------------------------------------------------------------------- /samples/django/django_user/myoauth/wsgi.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | 17 | from django.core.wsgi import get_wsgi_application 18 | 19 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myoauth.settings") 20 | 21 | application = get_wsgi_application() 22 | -------------------------------------------------------------------------------- /samples/django/django_user/polls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/oauth2client/50d20532a748f18e53f7d24ccbe6647132c979a9/samples/django/django_user/polls/__init__.py -------------------------------------------------------------------------------- /samples/django/django_user/polls/models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from django.contrib.auth.models import User 16 | from django.db import models 17 | 18 | from oauth2client.contrib.django_util.models import CredentialsField 19 | 20 | 21 | class CredentialsModel(models.Model): 22 | user_id = models.OneToOneField(User) 23 | credential = CredentialsField() 24 | -------------------------------------------------------------------------------- /samples/django/django_user/polls/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | {% if form.errors %} 18 |

Your username and password didn't match. Please try again.

19 | {% endif %} 20 | 21 | {% if next %} 22 | {% if user.is_authenticated %} 23 |

Your account doesn't have access to this page. To proceed, 24 | please login with an account that has access.

25 | {% else %} 26 |

Please login to see this page.

27 | {% endif %} 28 | {% endif %} 29 | 30 |
31 | {% csrf_token %} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
{{ form.username.label_tag }}{{ form.username }}
{{ form.password.label_tag }}{{ form.password }}
42 | 43 | 44 | 45 |
46 | -------------------------------------------------------------------------------- /samples/django/django_user/polls/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from django.http import HttpResponse 16 | 17 | from oauth2client.contrib.django_util import decorators 18 | 19 | 20 | def index(request): 21 | return HttpResponse("Hello world!") 22 | 23 | 24 | @decorators.oauth_required 25 | def get_profile_required(request): 26 | resp, content = request.oauth.http.request( 27 | 'https://www.googleapis.com/plus/v1/people/me') 28 | return HttpResponse(content) 29 | 30 | 31 | @decorators.oauth_enabled 32 | def get_profile_optional(request): 33 | if request.oauth.has_credentials(): 34 | # this could be passed into a view 35 | # request.oauth.http is also initialized 36 | return HttpResponse('User email: {}'.format( 37 | request.oauth.credentials.id_token['email'])) 38 | else: 39 | return HttpResponse( 40 | 'Here is an OAuth Authorize link:Authorize' 41 | .format(request.oauth.get_authorize_redirect())) 42 | -------------------------------------------------------------------------------- /samples/django/django_user/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.10.0 2 | oauth2client==3.0.0 3 | jsonpickle==0.9.3 4 | -------------------------------------------------------------------------------- /samples/django/google_user/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2016 Google Inc. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | import os 16 | import sys 17 | 18 | if __name__ == "__main__": 19 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myoauth.settings") 20 | 21 | from django.core.management import execute_from_command_line 22 | 23 | execute_from_command_line(sys.argv) 24 | -------------------------------------------------------------------------------- /samples/django/google_user/myoauth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/oauth2client/50d20532a748f18e53f7d24ccbe6647132c979a9/samples/django/google_user/myoauth/__init__.py -------------------------------------------------------------------------------- /samples/django/google_user/myoauth/settings.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | import os 17 | 18 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = 'eiw+mvmua#98n@p2xq+c#liz@r2&#-s07nkgz)+$zcl^o4$-$o' 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = [] 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = ( 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'polls', 41 | 'oauth2client.contrib.django_util', 42 | ) 43 | 44 | MIDDLEWARE_CLASSES = ( 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | 'django.middleware.security.SecurityMiddleware', 52 | ) 53 | 54 | ROOT_URLCONF = 'myoauth.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'myoauth.wsgi.application' 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 81 | } 82 | } 83 | 84 | # Internationalization 85 | # https://docs.djangoproject.com/en/1.8/topics/i18n/ 86 | 87 | LANGUAGE_CODE = 'en-us' 88 | 89 | TIME_ZONE = 'UTC' 90 | 91 | USE_I18N = True 92 | 93 | USE_L10N = True 94 | 95 | USE_TZ = True 96 | 97 | # Static files (CSS, JavaScript, Images) 98 | # https://docs.djangoproject.com/en/1.8/howto/static-files/ 99 | 100 | STATIC_URL = '/static/' 101 | 102 | GOOGLE_OAUTH2_CLIENT_ID = 'YOUR_CLIENT_ID' 103 | 104 | GOOGLE_OAUTH2_CLIENT_SECRET = 'YOUR_CLIENT_SECRET' 105 | 106 | GOOGLE_OAUTH2_SCOPES = ( 107 | 'email', 'profile') 108 | -------------------------------------------------------------------------------- /samples/django/google_user/myoauth/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from django.conf import urls 16 | from polls import views 17 | 18 | import oauth2client.contrib.django_util.site as django_util_site 19 | 20 | 21 | urlpatterns = [ 22 | urls.url(r'^$', views.index), 23 | urls.url(r'^profile_required$', views.get_profile_required), 24 | urls.url(r'^profile_enabled$', views.get_profile_optional), 25 | urls.url(r'^oauth2/', urls.include(django_util_site.urls)) 26 | ] 27 | -------------------------------------------------------------------------------- /samples/django/google_user/myoauth/wsgi.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | 17 | from django.core.wsgi import get_wsgi_application 18 | 19 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myoauth.settings") 20 | 21 | application = get_wsgi_application() 22 | -------------------------------------------------------------------------------- /samples/django/google_user/polls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/oauth2client/50d20532a748f18e53f7d24ccbe6647132c979a9/samples/django/google_user/polls/__init__.py -------------------------------------------------------------------------------- /samples/django/google_user/polls/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from django import http 16 | 17 | from oauth2client.contrib.django_util import decorators 18 | 19 | 20 | def index(request): 21 | return http.HttpResponse("Hello world!") 22 | 23 | 24 | @decorators.oauth_required 25 | def get_profile_required(request): 26 | resp, content = request.oauth.http.request( 27 | 'https://www.googleapis.com/plus/v1/people/me') 28 | return http.HttpResponse(content) 29 | 30 | 31 | @decorators.oauth_enabled 32 | def get_profile_optional(request): 33 | if request.oauth.has_credentials(): 34 | # this could be passed into a view 35 | # request.oauth.http is also initialized 36 | return http.HttpResponse('User email: {}'.format( 37 | request.oauth.credentials.id_token['email'])) 38 | else: 39 | return http.HttpResponse( 40 | 'Here is an OAuth Authorize link:Authorize' 41 | .format(request.oauth.get_authorize_redirect())) 42 | -------------------------------------------------------------------------------- /samples/django/google_user/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.10.0 2 | oauth2client==3.0.0 3 | jsonpickle==0.9.3 4 | -------------------------------------------------------------------------------- /samples/googleappengine/app.yaml: -------------------------------------------------------------------------------- 1 | application: bamboo-machine-422 2 | version: 2 3 | runtime: python27 4 | api_version: 1 5 | threadsafe: true 6 | 7 | handlers: 8 | - url: /.* 9 | script: call_compute_service_from_gae.app 10 | 11 | -------------------------------------------------------------------------------- /samples/googleappengine/call_compute_service_from_gae.py: -------------------------------------------------------------------------------- 1 | # To be used to test GoogleCredentials.get_application_default() 2 | # from devel GAE (ie, dev_appserver.py). 3 | 4 | from googleapiclient.discovery import build 5 | import webapp2 6 | 7 | from oauth2client.client import GoogleCredentials 8 | 9 | 10 | PROJECT = 'bamboo-machine-422' # Provide your own GCE project here 11 | ZONE = 'us-central1-a' # Put here a zone which has some VMs 12 | 13 | 14 | def get_instances(): 15 | credentials = GoogleCredentials.get_application_default() 16 | service = build('compute', 'v1', credentials=credentials) 17 | request = service.instances().list(project=PROJECT, zone=ZONE) 18 | return request.execute() 19 | 20 | 21 | class MainPage(webapp2.RequestHandler): 22 | 23 | def get(self): 24 | self.response.write(get_instances()) 25 | 26 | 27 | app = webapp2.WSGIApplication([('/', MainPage), ], debug=True) 28 | -------------------------------------------------------------------------------- /samples/oauth2_for_devices.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # See: https://developers.google.com/accounts/docs/OAuth2ForDevices 4 | 5 | from googleapiclient.discovery import build 6 | import httplib2 7 | from six.moves import input 8 | 9 | from oauth2client.client import OAuth2WebServerFlow 10 | 11 | CLIENT_ID = "some+client+id" 12 | CLIENT_SECRET = "some+client+secret" 13 | SCOPES = ("https://www.googleapis.com/auth/youtube",) 14 | 15 | flow = OAuth2WebServerFlow(CLIENT_ID, CLIENT_SECRET, " ".join(SCOPES)) 16 | 17 | # Step 1: get user code and verification URL 18 | # https://developers.google.com/accounts/docs/OAuth2ForDevices#obtainingacode 19 | flow_info = flow.step1_get_device_and_user_codes() 20 | print("Enter the following code at {0}: {1}".format(flow_info.verification_url, 21 | flow_info.user_code)) 22 | print("Then press Enter.") 23 | input() 24 | 25 | # Step 2: get credentials 26 | # https://developers.google.com/accounts/docs/OAuth2ForDevices#obtainingatoken 27 | credentials = flow.step2_exchange(device_flow_info=flow_info) 28 | print("Access token: {0}".format(credentials.access_token)) 29 | print("Refresh token: {0}".format(credentials.refresh_token)) 30 | 31 | # Get YouTube service 32 | # https://developers.google.com/accounts/docs/OAuth2ForDevices#callinganapi 33 | youtube = build("youtube", "v3", http=credentials.authorize(httplib2.Http())) 34 | -------------------------------------------------------------------------------- /scripts/build_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2014 Google Inc. All rights reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # Build the oauth2client docs. 18 | 19 | set -e 20 | 21 | rm -rf docs/_build/* docs/source/* 22 | sphinx-apidoc --separate --force -o docs/source oauth2client 23 | # We only have one package, so modules.rst is overkill. 24 | rm -f docs/source/modules.rst 25 | 26 | # If anything has changed 27 | if [[ -n "$(git diff -- docs/)" ]]; then 28 | echo "sphinx-apidoc generated changes that are not checked in to version control." 29 | exit 1 30 | fi 31 | 32 | cd docs 33 | make html 34 | cd .. 35 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2015 Google Inc. All rights reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -ev 18 | 19 | pip install --upgrade pip setuptools tox coveralls 20 | 21 | # App Engine tests require the App Engine SDK. 22 | if [[ "${TOX_ENV}" == "gae" || "${TOX_ENV}" == "cover" ]]; then 23 | pip install gcp-devrel-py-tools 24 | gcp-devrel-py-tools download-appengine-sdk `dirname ${GAE_PYTHONPATH}` 25 | fi 26 | 27 | # Travis ships with an old version of PyPy, so install at least version 2.6. 28 | if [[ "${TOX_ENV}" == "pypy" ]]; then 29 | if [ ! -d "${HOME}/.pyenv/bin" ]; then 30 | git clone https://github.com/yyuu/pyenv.git ${HOME}/.pyenv 31 | fi 32 | ${HOME}/.pyenv/bin/pyenv install --skip-existing pypy-2.6.0 33 | fi 34 | -------------------------------------------------------------------------------- /scripts/local_test_setup.sample: -------------------------------------------------------------------------------- 1 | export OAUTH2CLIENT_TEST_JSON_KEY_PATH="tests/data/gcloud/application_default_credentials.json" 2 | export OAUTH2CLIENT_TEST_P12_KEY_PATH="tests/data/privatekey.p12" 3 | export OAUTH2CLIENT_TEST_P12_KEY_EMAIL="project-foo@developer.gserviceaccount.com" 4 | export OAUTH2CLIENT_TEST_USER_KEY_PATH="tests/data/gcloud/application_default_credentials_authorized_user.json" 5 | export OAUTH2CLIENT_TEST_USER_KEY_EMAIL="foo@gmail.com" 6 | -------------------------------------------------------------------------------- /scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2015 Google Inc. All rights reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -ev 18 | 19 | # If in the pypy environment, activate the never version of pypy provided by 20 | # pyenv. 21 | if [[ "${TOX_ENV}" == "pypy" ]]; then 22 | PATH="${HOME}/.pyenv/versions/pypy-2.6.0/bin:${PATH}" 23 | export PATH 24 | fi 25 | 26 | tox -e ${TOX_ENV} 27 | -------------------------------------------------------------------------------- /scripts/run_gce_system_tests.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import json 16 | import unittest 17 | 18 | from six.moves import http_client 19 | from six.moves import urllib 20 | 21 | import oauth2client 22 | from oauth2client import client 23 | from oauth2client import transport 24 | from oauth2client.contrib import gce 25 | 26 | 27 | class TestComputeEngine(unittest.TestCase): 28 | 29 | def test_application_default(self): 30 | default_creds = client.GoogleCredentials.get_application_default() 31 | self.assertIsInstance(default_creds, gce.AppAssertionCredentials) 32 | 33 | def test_token_info(self): 34 | credentials = gce.AppAssertionCredentials([]) 35 | http = transport.get_http_object() 36 | 37 | # First refresh to get the access token. 38 | self.assertIsNone(credentials.access_token) 39 | credentials.refresh(http) 40 | self.assertIsNotNone(credentials.access_token) 41 | 42 | # Then check the access token against the token info API. 43 | query_params = {'access_token': credentials.access_token} 44 | token_uri = (oauth2client.GOOGLE_TOKEN_INFO_URI + '?' + 45 | urllib.parse.urlencode(query_params)) 46 | response, content = transport.request(http, token_uri) 47 | self.assertEqual(response.status, http_client.OK) 48 | 49 | content = content.decode('utf-8') 50 | payload = json.loads(content) 51 | self.assertEqual(payload['access_type'], 'offline') 52 | self.assertLessEqual(int(payload['expires_in']), 3600) 53 | 54 | 55 | if __name__ == '__main__': 56 | unittest.main() 57 | -------------------------------------------------------------------------------- /scripts/run_system_tests.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import json 16 | import os 17 | 18 | from six.moves import http_client 19 | 20 | import oauth2client 21 | from oauth2client import client 22 | from oauth2client import service_account 23 | from oauth2client import transport 24 | 25 | 26 | JSON_KEY_PATH = os.getenv('OAUTH2CLIENT_TEST_JSON_KEY_PATH') 27 | P12_KEY_PATH = os.getenv('OAUTH2CLIENT_TEST_P12_KEY_PATH') 28 | P12_KEY_EMAIL = os.getenv('OAUTH2CLIENT_TEST_P12_KEY_EMAIL') 29 | USER_KEY_PATH = os.getenv('OAUTH2CLIENT_TEST_USER_KEY_PATH') 30 | USER_KEY_EMAIL = os.getenv('OAUTH2CLIENT_TEST_USER_KEY_EMAIL') 31 | 32 | SCOPE = ('https://www.googleapis.com/auth/plus.login', 33 | 'https://www.googleapis.com/auth/plus.me', 34 | 'https://www.googleapis.com/auth/userinfo.email', 35 | 'https://www.googleapis.com/auth/userinfo.profile') 36 | USER_INFO = 'https://www.googleapis.com/oauth2/v2/userinfo' 37 | 38 | 39 | def _require_environ(): 40 | if (JSON_KEY_PATH is None or P12_KEY_PATH is None or 41 | P12_KEY_EMAIL is None or USER_KEY_PATH is None or 42 | USER_KEY_EMAIL is None): 43 | raise EnvironmentError('Expected environment variables to be set:', 44 | 'OAUTH2CLIENT_TEST_JSON_KEY_PATH', 45 | 'OAUTH2CLIENT_TEST_P12_KEY_PATH', 46 | 'OAUTH2CLIENT_TEST_P12_KEY_EMAIL', 47 | 'OAUTH2CLIENT_TEST_USER_KEY_PATH', 48 | 'OAUTH2CLIENT_TEST_USER_KEY_EMAIL') 49 | 50 | if not os.path.isfile(JSON_KEY_PATH): 51 | raise EnvironmentError(JSON_KEY_PATH, 'is not a file') 52 | if not os.path.isfile(P12_KEY_PATH): 53 | raise EnvironmentError(P12_KEY_PATH, 'is not a file') 54 | if not os.path.isfile(USER_KEY_PATH): 55 | raise EnvironmentError(USER_KEY_PATH, 'is not a file') 56 | 57 | 58 | def _check_user_info(credentials, expected_email): 59 | http = credentials.authorize(transport.get_http_object()) 60 | response, content = transport.request(http, USER_INFO) 61 | if response.status != http_client.OK: 62 | raise ValueError('Expected 200 OK response.') 63 | 64 | content = content.decode('utf-8') 65 | payload = json.loads(content) 66 | if payload['email'] != expected_email: 67 | raise ValueError('User info email does not match credentials.') 68 | 69 | 70 | def run_json(): 71 | factory = service_account.ServiceAccountCredentials.from_json_keyfile_name 72 | credentials = factory(JSON_KEY_PATH, scopes=SCOPE) 73 | service_account_email = credentials._service_account_email 74 | _check_user_info(credentials, service_account_email) 75 | 76 | 77 | def run_p12(): 78 | credentials = service_account.ServiceAccountCredentials.from_p12_keyfile( 79 | P12_KEY_EMAIL, P12_KEY_PATH, scopes=SCOPE) 80 | _check_user_info(credentials, P12_KEY_EMAIL) 81 | 82 | 83 | def run_user_json(): 84 | with open(USER_KEY_PATH, 'r') as file_object: 85 | client_credentials = json.load(file_object) 86 | 87 | credentials = client.GoogleCredentials( 88 | access_token=None, 89 | client_id=client_credentials['client_id'], 90 | client_secret=client_credentials['client_secret'], 91 | refresh_token=client_credentials['refresh_token'], 92 | token_expiry=None, 93 | token_uri=oauth2client.GOOGLE_TOKEN_URI, 94 | user_agent='Python client library', 95 | ) 96 | 97 | _check_user_info(credentials, USER_KEY_EMAIL) 98 | 99 | 100 | def main(): 101 | _require_environ() 102 | run_json() 103 | run_p12() 104 | run_user_json() 105 | 106 | 107 | if __name__ == '__main__': 108 | main() 109 | -------------------------------------------------------------------------------- /scripts/run_system_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2015 Google Inc. All rights reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -ev 18 | 19 | 20 | # If we're on Travis, we need to set up the environment. 21 | if [[ "${TRAVIS}" == "true" ]]; then 22 | # If secure variables are available, run system test. 23 | if [[ "${TRAVIS_SECURE_ENV_VARS}" ]]; then 24 | echo "Running in Travis, decrypting stored key file." 25 | # Convert encrypted JSON key file into decrypted file to be used. 26 | openssl aes-256-cbc -K ${OAUTH2CLIENT_KEY} \ 27 | -iv ${OAUTH2CLIENT_IV} \ 28 | -in tests/data/key.json.enc \ 29 | -out ${OAUTH2CLIENT_TEST_JSON_KEY_PATH} -d 30 | # Convert encrypted P12 key file into decrypted file to be used. 31 | openssl aes-256-cbc -K ${OAUTH2CLIENT_KEY} \ 32 | -iv ${OAUTH2CLIENT_IV} \ 33 | -in tests/data/key.p12.enc \ 34 | -out ${OAUTH2CLIENT_TEST_P12_KEY_PATH} -d 35 | # Convert encrypted User JSON key file into decrypted file to be used. 36 | openssl aes-256-cbc -K ${encrypted_1ee98544e5ca_key} \ 37 | -iv ${encrypted_1ee98544e5ca_iv} \ 38 | -in tests/data/user-key.json.enc \ 39 | -out ${OAUTH2CLIENT_TEST_USER_KEY_PATH} -d 40 | else 41 | echo "Running in Travis during non-merge to master, doing nothing." 42 | exit 43 | fi 44 | fi 45 | 46 | # Run the system tests for each tested package. 47 | python scripts/run_system_tests.py 48 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Setup script for oauth2client. 16 | 17 | Also installs included versions of third party libraries, if those libraries 18 | are not already installed. 19 | """ 20 | from __future__ import print_function 21 | 22 | import sys 23 | 24 | from setuptools import find_packages 25 | from setuptools import setup 26 | 27 | import oauth2client 28 | 29 | if sys.version_info < (2, 7): 30 | print('oauth2client requires python2 version >= 2.7.', file=sys.stderr) 31 | sys.exit(1) 32 | if (3, 1) <= sys.version_info < (3, 4): 33 | print('oauth2client requires python3 version >= 3.4.', file=sys.stderr) 34 | sys.exit(1) 35 | 36 | install_requires = [ 37 | 'httplib2>=0.9.1', 38 | 'pyasn1>=0.1.7', 39 | 'pyasn1-modules>=0.0.5', 40 | 'rsa>=3.1.4', 41 | 'six>=1.6.1', 42 | ] 43 | 44 | long_desc = """ 45 | oauth2client is a client library for OAuth 2.0. 46 | 47 | Note: oauth2client is now deprecated. No more features will be added to the 48 | libraries and the core team is turning down support. We recommend you use 49 | `google-auth `__ and 50 | `oauthlib `__. 51 | """ 52 | 53 | version = oauth2client.__version__ 54 | 55 | setup( 56 | name='oauth2client', 57 | version=version, 58 | description='OAuth 2.0 client library', 59 | long_description=long_desc, 60 | author='Google Inc.', 61 | author_email='jonwayne+oauth2client@google.com', 62 | url='http://github.com/google/oauth2client/', 63 | install_requires=install_requires, 64 | packages=find_packages(exclude=('tests*',)), 65 | license='Apache 2.0', 66 | keywords='google oauth 2.0 http client', 67 | classifiers=[ 68 | 'Programming Language :: Python :: 2', 69 | 'Programming Language :: Python :: 2.7', 70 | 'Programming Language :: Python :: 3', 71 | 'Programming Language :: Python :: 3.4', 72 | 'Programming Language :: Python :: 3.5', 73 | 'Development Status :: 7 - Inactive', 74 | 'Intended Audience :: Developers', 75 | 'License :: OSI Approved :: Apache Software License', 76 | 'Operating System :: POSIX', 77 | 'Topic :: Internet :: WWW/HTTP', 78 | ], 79 | ) 80 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/oauth2client/50d20532a748f18e53f7d24ccbe6647132c979a9/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Py.test hooks.""" 16 | 17 | from oauth2client import _helpers 18 | 19 | 20 | def pytest_addoption(parser): 21 | """Adds the --gae-sdk option to py.test. 22 | 23 | This is used to enable the GAE tests. This has to be in this conftest.py 24 | due to the way py.test collects conftest files.""" 25 | parser.addoption('--gae-sdk') 26 | 27 | 28 | def pytest_configure(config): 29 | """Py.test hook called before loading tests.""" 30 | # Default of POSITIONAL_WARNING is too verbose for testing 31 | _helpers.positional_parameters_enforcement = _helpers.POSITIONAL_EXCEPTION 32 | -------------------------------------------------------------------------------- /tests/contrib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/oauth2client/50d20532a748f18e53f7d24ccbe6647132c979a9/tests/contrib/__init__.py -------------------------------------------------------------------------------- /tests/contrib/appengine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/oauth2client/50d20532a748f18e53f7d24ccbe6647132c979a9/tests/contrib/appengine/__init__.py -------------------------------------------------------------------------------- /tests/contrib/appengine/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """App Engine py.test configuration.""" 16 | 17 | import sys 18 | 19 | from six.moves import reload_module 20 | 21 | 22 | def set_up_gae_environment(sdk_path): 23 | """Set up appengine SDK third-party imports. 24 | 25 | The App Engine SDK does terrible things to the global interpreter state. 26 | Because of this, this stuff can't be neatly undone. As such, it can't be 27 | a fixture. 28 | """ 29 | if 'google' in sys.modules: 30 | # Some packages, such as protobuf, clobber the google 31 | # namespace package. This prevents that. 32 | reload_module(sys.modules['google']) 33 | 34 | # This sets up google-provided libraries. 35 | sys.path.insert(0, sdk_path) 36 | import dev_appserver 37 | dev_appserver.fix_sys_path() 38 | 39 | # Fixes timezone and other os-level items. 40 | import google.appengine.tools.os_compat # noqa: unused import 41 | 42 | 43 | def pytest_configure(config): 44 | """Configures the App Engine SDK imports on py.test startup.""" 45 | if config.getoption('gae_sdk') is not None: 46 | set_up_gae_environment(config.getoption('gae_sdk')) 47 | 48 | 49 | def pytest_ignore_collect(path, config): 50 | """Skip App Engine tests when --gae-sdk is not specified.""" 51 | return ( 52 | 'contrib/appengine' in str(path) and 53 | config.getoption('gae_sdk') is None) 54 | -------------------------------------------------------------------------------- /tests/contrib/django_util/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Setups the Django test environment and provides helper classes.""" 16 | 17 | import django 18 | from django import test 19 | from django.contrib.sessions.backends.file import SessionStore 20 | from django.test.runner import DiscoverRunner 21 | 22 | django.setup() 23 | default_app_config = 'tests.contrib.django_util.apps.AppConfig' 24 | 25 | 26 | class TestWithDjangoEnvironment(test.TestCase): 27 | @classmethod 28 | def setUpClass(cls): 29 | django.setup() 30 | cls.runner = DiscoverRunner() 31 | cls.runner.setup_test_environment() 32 | cls.old_config = cls.runner.setup_databases() 33 | 34 | @classmethod 35 | def tearDownClass(cls): 36 | cls.runner.teardown_databases(cls.old_config) 37 | cls.runner.teardown_test_environment() 38 | 39 | def setUp(self): 40 | self.factory = test.RequestFactory() 41 | 42 | store = SessionStore() 43 | store.save() 44 | self.session = store 45 | -------------------------------------------------------------------------------- /tests/contrib/django_util/apps.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Defines a configuration for our test application. 16 | 17 | Having a test application enables us to use the Django test database and 18 | other useful features.""" 19 | 20 | from django.apps import AppConfig 21 | 22 | 23 | class DjangoOrmTestApp(AppConfig): 24 | """App Config for Django Helper.""" 25 | name = 'tests.contrib.django_util' 26 | verbose_name = "Django Test App" 27 | label = "DjangoORMTestApp" 28 | -------------------------------------------------------------------------------- /tests/contrib/django_util/models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Models used in our tests""" 16 | 17 | from django.contrib.auth.models import User 18 | from django.db import models 19 | 20 | from oauth2client.contrib.django_util.models import CredentialsField 21 | 22 | 23 | class CredentialsModel(models.Model): 24 | user_id = models.OneToOneField(User) 25 | credentials = CredentialsField() 26 | -------------------------------------------------------------------------------- /tests/contrib/django_util/settings.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Provides a base Django settings module used by the rest of the tests.""" 16 | 17 | import os 18 | 19 | INSTALLED_APPS = [ 20 | 'django.contrib.admin', 21 | 'django.contrib.auth', 22 | 'django.contrib.contenttypes', 23 | 'django.contrib.sessions', 24 | 'django.contrib.messages', 25 | 'django.contrib.staticfiles', 26 | 'oauth2client.contrib.django_util', 27 | 'tests.contrib.django_util.apps.DjangoOrmTestApp', 28 | ] 29 | 30 | SECRET_KEY = 'this string is not a real django secret key' 31 | DATABASES = { 32 | 'default': { 33 | 'ENGINE': 'django.db.backends.sqlite3', 34 | 'NAME': os.path.join('.', 'db.sqlite3'), 35 | } 36 | } 37 | MIDDLEWARE_CLASSES = ( 38 | 'django.contrib.sessions.middleware.SessionMiddleware' 39 | ) 40 | 41 | ALLOWED_HOSTS = ['testserver'] 42 | 43 | GOOGLE_OAUTH2_CLIENT_ID = 'client_id2' 44 | GOOGLE_OAUTH2_CLIENT_SECRET = 'hunter2' 45 | GOOGLE_OAUTH2_SCOPES = ('https://www.googleapis.com/auth/cloud-platform',) 46 | 47 | ROOT_URLCONF = 'tests.contrib.django_util.test_django_util' 48 | -------------------------------------------------------------------------------- /tests/contrib/django_util/test_django_models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Django model tests. 16 | 17 | Unit tests for models and fields defined by the django_util helper. 18 | """ 19 | 20 | import base64 21 | import pickle 22 | import unittest 23 | 24 | import jsonpickle 25 | 26 | from oauth2client import _helpers 27 | from oauth2client import client 28 | from oauth2client.contrib.django_util import models 29 | from tests.contrib.django_util import models as tests_models 30 | 31 | 32 | class TestCredentialsField(unittest.TestCase): 33 | 34 | def setUp(self): 35 | self.fake_model = tests_models.CredentialsModel() 36 | self.fake_model_field = self.fake_model._meta.get_field('credentials') 37 | self.field = models.CredentialsField(null=True) 38 | self.credentials = client.Credentials() 39 | self.pickle_str = _helpers._from_bytes( 40 | base64.b64encode(pickle.dumps(self.credentials))) 41 | self.jsonpickle_str = _helpers._from_bytes( 42 | base64.b64encode(jsonpickle.encode(self.credentials).encode())) 43 | 44 | def test_field_is_text(self): 45 | self.assertEqual(self.field.get_internal_type(), 'BinaryField') 46 | 47 | def test_field_unpickled(self): 48 | self.assertIsInstance( 49 | self.field.to_python(self.pickle_str), client.Credentials) 50 | 51 | def test_field_jsonunpickled(self): 52 | self.assertIsInstance( 53 | self.field.to_python(self.jsonpickle_str), client.Credentials) 54 | 55 | def test_field_already_unpickled(self): 56 | self.assertIsInstance( 57 | self.field.to_python(self.credentials), client.Credentials) 58 | 59 | def test_none_field_unpickled(self): 60 | self.assertIsNone(self.field.to_python(None)) 61 | 62 | def test_from_db_value(self): 63 | value = self.field.from_db_value( 64 | self.pickle_str, None, None, None) 65 | self.assertIsInstance(value, client.Credentials) 66 | 67 | def test_field_unpickled_none(self): 68 | self.assertEqual(self.field.to_python(None), None) 69 | 70 | def test_field_pickled(self): 71 | prep_value = self.field.get_db_prep_value(self.credentials, 72 | connection=None) 73 | self.assertEqual(prep_value, self.jsonpickle_str) 74 | 75 | def test_field_value_to_string(self): 76 | self.fake_model.credentials = self.credentials 77 | value_str = self.fake_model_field.value_to_string(self.fake_model) 78 | self.assertEqual(value_str, self.jsonpickle_str) 79 | 80 | def test_field_value_to_string_none(self): 81 | self.fake_model.credentials = None 82 | value_str = self.fake_model_field.value_to_string(self.fake_model) 83 | self.assertIsNone(value_str) 84 | 85 | def test_credentials_without_null(self): 86 | credentials = models.CredentialsField() 87 | self.assertTrue(credentials.null) 88 | 89 | 90 | class CredentialWithSetStore(models.CredentialsField): 91 | def __init__(self): 92 | self.model = CredentialWithSetStore 93 | 94 | def set_store(self, storage): 95 | pass # pragma: NO COVER 96 | 97 | 98 | class FakeCredentialsModelMock(object): 99 | 100 | credentials = CredentialWithSetStore() 101 | 102 | 103 | class FakeCredentialsModelMockNoSet(object): 104 | 105 | credentials = models.CredentialsField() 106 | -------------------------------------------------------------------------------- /tests/contrib/django_util/test_django_storage.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Tests for the DjangoORM storage class.""" 16 | 17 | # Mock a Django environment 18 | import datetime 19 | import unittest 20 | 21 | from django.db import models 22 | import mock 23 | 24 | from oauth2client import GOOGLE_TOKEN_URI 25 | from oauth2client.client import OAuth2Credentials 26 | from oauth2client.contrib.django_util.models import CredentialsField 27 | from oauth2client.contrib.django_util.storage import ( 28 | DjangoORMStorage as Storage) 29 | 30 | 31 | class TestStorage(unittest.TestCase): 32 | def setUp(self): 33 | access_token = 'foo' 34 | client_id = 'some_client_id' 35 | client_secret = 'cOuDdkfjxxnv+' 36 | refresh_token = '1/0/a.df219fjls0' 37 | token_expiry = datetime.datetime.utcnow() 38 | user_agent = 'refresh_checker/1.0' 39 | 40 | self.credentials = OAuth2Credentials( 41 | access_token, client_id, client_secret, 42 | refresh_token, token_expiry, GOOGLE_TOKEN_URI, 43 | user_agent) 44 | 45 | self.key_name = 'id' 46 | self.key_value = '1' 47 | self.property_name = 'credentials' 48 | 49 | def test_constructor(self): 50 | storage = Storage(FakeCredentialsModel, self.key_name, 51 | self.key_value, self.property_name) 52 | 53 | self.assertEqual(storage.model_class, FakeCredentialsModel) 54 | self.assertEqual(storage.key_name, self.key_name) 55 | self.assertEqual(storage.key_value, self.key_value) 56 | self.assertEqual(storage.property_name, self.property_name) 57 | 58 | @mock.patch('django.db.models') 59 | def test_locked_get(self, djangoModel): 60 | fake_model_with_credentials = FakeCredentialsModelMock() 61 | entities = [ 62 | fake_model_with_credentials 63 | ] 64 | filter_mock = mock.Mock(return_value=entities) 65 | object_mock = mock.Mock() 66 | object_mock.filter = filter_mock 67 | FakeCredentialsModelMock.objects = object_mock 68 | 69 | storage = Storage(FakeCredentialsModelMock, self.key_name, 70 | self.key_value, self.property_name) 71 | credential = storage.locked_get() 72 | self.assertEqual( 73 | credential, fake_model_with_credentials.credentials) 74 | 75 | @mock.patch('django.db.models') 76 | def test_locked_get_no_entities(self, djangoModel): 77 | entities = [] 78 | filter_mock = mock.Mock(return_value=entities) 79 | object_mock = mock.Mock() 80 | object_mock.filter = filter_mock 81 | FakeCredentialsModelMock.objects = object_mock 82 | 83 | storage = Storage(FakeCredentialsModelMock, self.key_name, 84 | self.key_value, self.property_name) 85 | credential = storage.locked_get() 86 | self.assertIsNone(credential) 87 | 88 | @mock.patch('django.db.models') 89 | def test_locked_get_no_set_store(self, djangoModel): 90 | fake_model_with_credentials = FakeCredentialsModelMockNoSet() 91 | entities = [ 92 | fake_model_with_credentials 93 | ] 94 | filter_mock = mock.Mock(return_value=entities) 95 | object_mock = mock.Mock() 96 | object_mock.filter = filter_mock 97 | FakeCredentialsModelMockNoSet.objects = object_mock 98 | 99 | storage = Storage(FakeCredentialsModelMockNoSet, self.key_name, 100 | self.key_value, self.property_name) 101 | credential = storage.locked_get() 102 | self.assertEqual( 103 | credential, fake_model_with_credentials.credentials) 104 | 105 | @mock.patch('django.db.models') 106 | def test_locked_put(self, djangoModel): 107 | entity_mock = mock.Mock(credentials=None) 108 | objects = mock.Mock( 109 | get_or_create=mock.Mock(return_value=(entity_mock, None))) 110 | FakeCredentialsModelMock.objects = objects 111 | storage = Storage(FakeCredentialsModelMock, self.key_name, 112 | self.key_value, self.property_name) 113 | storage.locked_put(self.credentials) 114 | 115 | @mock.patch('django.db.models') 116 | def test_locked_delete(self, djangoModel): 117 | class FakeEntities(object): 118 | def __init__(self): 119 | self.deleted = False 120 | 121 | def delete(self): 122 | self.deleted = True 123 | 124 | fake_entities = FakeEntities() 125 | entities = fake_entities 126 | 127 | filter_mock = mock.Mock(return_value=entities) 128 | object_mock = mock.Mock() 129 | object_mock.filter = filter_mock 130 | FakeCredentialsModelMock.objects = object_mock 131 | storage = Storage(FakeCredentialsModelMock, self.key_name, 132 | self.key_value, self.property_name) 133 | storage.locked_delete() 134 | self.assertTrue(fake_entities.deleted) 135 | 136 | 137 | class CredentialWithSetStore(CredentialsField): 138 | def __init__(self): 139 | self.model = CredentialWithSetStore 140 | 141 | def set_store(self, storage): 142 | pass 143 | 144 | 145 | class FakeCredentialsModel(models.Model): 146 | credentials = CredentialsField() 147 | 148 | 149 | class FakeCredentialsModelMock(object): 150 | def __init__(self, *args, **kwargs): 151 | self.model = FakeCredentialsModelMock 152 | self.saved = False 153 | self.deleted = False 154 | 155 | credentials = CredentialWithSetStore() 156 | 157 | 158 | class FakeCredentialsModelMockNoSet(object): 159 | def __init__(self, set_store=False, *args, **kwargs): 160 | self.model = FakeCredentialsModelMock 161 | self.saved = False 162 | self.deleted = False 163 | 164 | credentials = CredentialsField() 165 | -------------------------------------------------------------------------------- /tests/contrib/test_dictionary_storage.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Unit tests for oauth2client.contrib.dictionary_storage""" 16 | 17 | import unittest 18 | 19 | import oauth2client 20 | from oauth2client import client 21 | from oauth2client.contrib import dictionary_storage 22 | 23 | 24 | def _generate_credentials(scopes=None): 25 | return client.OAuth2Credentials( 26 | 'access_tokenz', 27 | 'client_idz', 28 | 'client_secretz', 29 | 'refresh_tokenz', 30 | '3600', 31 | oauth2client.GOOGLE_TOKEN_URI, 32 | 'Test', 33 | id_token={ 34 | 'sub': '123', 35 | 'email': 'user@example.com' 36 | }, 37 | scopes=scopes) 38 | 39 | 40 | class DictionaryStorageTests(unittest.TestCase): 41 | 42 | def test_constructor_defaults(self): 43 | dictionary = {} 44 | key = 'test-key' 45 | storage = dictionary_storage.DictionaryStorage(dictionary, key) 46 | 47 | self.assertEqual(dictionary, storage._dictionary) 48 | self.assertEqual(key, storage._key) 49 | self.assertIsNone(storage._lock) 50 | 51 | def test_constructor_explicit(self): 52 | dictionary = {} 53 | key = 'test-key' 54 | storage = dictionary_storage.DictionaryStorage(dictionary, key) 55 | 56 | lock = object() 57 | storage = dictionary_storage.DictionaryStorage( 58 | dictionary, key, lock=lock) 59 | self.assertEqual(storage._lock, lock) 60 | 61 | def test_get(self): 62 | credentials = _generate_credentials() 63 | dictionary = {} 64 | key = 'credentials' 65 | storage = dictionary_storage.DictionaryStorage(dictionary, key) 66 | 67 | self.assertIsNone(storage.get()) 68 | 69 | dictionary[key] = credentials.to_json() 70 | returned = storage.get() 71 | 72 | self.assertIsNotNone(returned) 73 | self.assertEqual(returned.access_token, credentials.access_token) 74 | self.assertEqual(returned.id_token, credentials.id_token) 75 | self.assertEqual(returned.refresh_token, credentials.refresh_token) 76 | self.assertEqual(returned.client_id, credentials.client_id) 77 | 78 | def test_put(self): 79 | credentials = _generate_credentials() 80 | dictionary = {} 81 | key = 'credentials' 82 | storage = dictionary_storage.DictionaryStorage(dictionary, key) 83 | 84 | storage.put(credentials) 85 | returned = storage.get() 86 | 87 | self.assertIn(key, dictionary) 88 | self.assertIsNotNone(returned) 89 | self.assertEqual(returned.access_token, credentials.access_token) 90 | self.assertEqual(returned.id_token, credentials.id_token) 91 | self.assertEqual(returned.refresh_token, credentials.refresh_token) 92 | self.assertEqual(returned.client_id, credentials.client_id) 93 | 94 | def test_delete(self): 95 | credentials = _generate_credentials() 96 | dictionary = {} 97 | key = 'credentials' 98 | storage = dictionary_storage.DictionaryStorage(dictionary, key) 99 | 100 | storage.put(credentials) 101 | 102 | self.assertIn(key, dictionary) 103 | 104 | storage.delete() 105 | 106 | self.assertNotIn(key, dictionary) 107 | self.assertIsNone(storage.get()) 108 | -------------------------------------------------------------------------------- /tests/contrib/test_metadata.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import datetime 16 | import json 17 | import unittest 18 | 19 | import mock 20 | from six.moves import http_client 21 | 22 | from oauth2client.contrib import _metadata 23 | from tests import http_mock 24 | 25 | 26 | PATH = 'instance/service-accounts/default' 27 | DATA = {'foo': 'bar'} 28 | EXPECTED_URL = ( 29 | 'http://metadata.google.internal/computeMetadata/v1/instance' 30 | '/service-accounts/default') 31 | 32 | 33 | def request_mock(status, content_type, content): 34 | headers = {'status': status, 'content-type': content_type} 35 | http = http_mock.HttpMock(headers=headers, 36 | data=content.encode('utf-8')) 37 | return http 38 | 39 | 40 | class TestMetadata(unittest.TestCase): 41 | 42 | def test_get_success_json(self): 43 | http = request_mock( 44 | http_client.OK, 'application/json', json.dumps(DATA)) 45 | self.assertEqual( 46 | _metadata.get(http, PATH), 47 | DATA 48 | ) 49 | 50 | # Verify mocks. 51 | self.assertEqual(http.requests, 1) 52 | self.assertEqual(http.uri, EXPECTED_URL) 53 | self.assertEqual(http.method, 'GET') 54 | self.assertIsNone(http.body) 55 | self.assertEqual(http.headers, _metadata.METADATA_HEADERS) 56 | 57 | def test_get_success_string(self): 58 | http = request_mock( 59 | http_client.OK, 'text/html', '

Hello World!

') 60 | self.assertEqual( 61 | _metadata.get(http, PATH), 62 | '

Hello World!

' 63 | ) 64 | 65 | # Verify mocks. 66 | self.assertEqual(http.requests, 1) 67 | self.assertEqual(http.uri, EXPECTED_URL) 68 | self.assertEqual(http.method, 'GET') 69 | self.assertIsNone(http.body) 70 | self.assertEqual(http.headers, _metadata.METADATA_HEADERS) 71 | 72 | def test_get_failure(self): 73 | http = request_mock( 74 | http_client.NOT_FOUND, 'text/html', '

Error

') 75 | with self.assertRaises(http_client.HTTPException): 76 | _metadata.get(http, PATH) 77 | 78 | # Verify mocks. 79 | self.assertEqual(http.requests, 1) 80 | self.assertEqual(http.uri, EXPECTED_URL) 81 | self.assertEqual(http.method, 'GET') 82 | self.assertIsNone(http.body) 83 | self.assertEqual(http.headers, _metadata.METADATA_HEADERS) 84 | 85 | @mock.patch( 86 | 'oauth2client.client._UTCNOW', 87 | return_value=datetime.datetime.min) 88 | def test_get_token_success(self, now): 89 | http = request_mock( 90 | http_client.OK, 91 | 'application/json', 92 | json.dumps({'access_token': 'a', 'expires_in': 100}) 93 | ) 94 | token, expiry = _metadata.get_token(http=http) 95 | self.assertEqual(token, 'a') 96 | self.assertEqual( 97 | expiry, datetime.datetime.min + datetime.timedelta(seconds=100)) 98 | # Verify mocks. 99 | now.assert_called_once_with() 100 | self.assertEqual(http.requests, 1) 101 | self.assertEqual(http.uri, EXPECTED_URL + '/token') 102 | self.assertEqual(http.method, 'GET') 103 | self.assertIsNone(http.body) 104 | self.assertEqual(http.headers, _metadata.METADATA_HEADERS) 105 | 106 | def test_service_account_info(self): 107 | http = request_mock( 108 | http_client.OK, 'application/json', json.dumps(DATA)) 109 | info = _metadata.get_service_account_info(http) 110 | self.assertEqual(info, DATA) 111 | # Verify mock. 112 | self.assertEqual(http.requests, 1) 113 | self.assertEqual(http.uri, EXPECTED_URL + '/?recursive=True') 114 | self.assertEqual(http.method, 'GET') 115 | self.assertIsNone(http.body) 116 | self.assertEqual(http.headers, _metadata.METADATA_HEADERS) 117 | -------------------------------------------------------------------------------- /tests/contrib/test_sqlalchemy.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import datetime 16 | import unittest 17 | 18 | import mock 19 | import sqlalchemy 20 | import sqlalchemy.ext.declarative 21 | import sqlalchemy.orm 22 | 23 | import oauth2client 24 | import oauth2client.client 25 | import oauth2client.contrib.sqlalchemy 26 | 27 | Base = sqlalchemy.ext.declarative.declarative_base() 28 | 29 | 30 | class DummyModel(Base): 31 | __tablename__ = 'dummy' 32 | 33 | id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True) 34 | # we will query against this, because of ROWID 35 | key = sqlalchemy.Column(sqlalchemy.Integer) 36 | credentials = sqlalchemy.Column( 37 | oauth2client.contrib.sqlalchemy.CredentialsType) 38 | 39 | 40 | class TestSQLAlchemyStorage(unittest.TestCase): 41 | def setUp(self): 42 | engine = sqlalchemy.create_engine('sqlite://') 43 | Base.metadata.create_all(engine) 44 | 45 | self.session = sqlalchemy.orm.sessionmaker(bind=engine) 46 | self.credentials = oauth2client.client.OAuth2Credentials( 47 | access_token='token', 48 | client_id='client_id', 49 | client_secret='client_secret', 50 | refresh_token='refresh_token', 51 | token_expiry=datetime.datetime.utcnow(), 52 | token_uri=oauth2client.GOOGLE_TOKEN_URI, 53 | user_agent='DummyAgent', 54 | ) 55 | 56 | def tearDown(self): 57 | session = self.session() 58 | session.query(DummyModel).filter_by(key=1).delete() 59 | session.commit() 60 | 61 | def compare_credentials(self, result): 62 | self.assertEqual(result.access_token, self.credentials.access_token) 63 | self.assertEqual(result.client_id, self.credentials.client_id) 64 | self.assertEqual(result.client_secret, self.credentials.client_secret) 65 | self.assertEqual(result.refresh_token, self.credentials.refresh_token) 66 | self.assertEqual(result.token_expiry, self.credentials.token_expiry) 67 | self.assertEqual(result.token_uri, self.credentials.token_uri) 68 | self.assertEqual(result.user_agent, self.credentials.user_agent) 69 | 70 | @mock.patch('oauth2client.client.OAuth2Credentials.set_store') 71 | def test_get(self, set_store): 72 | session = self.session() 73 | credentials_storage = oauth2client.contrib.sqlalchemy.Storage( 74 | session=session, 75 | model_class=DummyModel, 76 | key_name='key', 77 | key_value=1, 78 | property_name='credentials', 79 | ) 80 | # No credentials stored 81 | self.assertIsNone(credentials_storage.get()) 82 | 83 | # Invalid credentials stored 84 | session.add(DummyModel( 85 | key=1, 86 | credentials=oauth2client.client.Credentials(), 87 | )) 88 | session.commit() 89 | bad_credentials = credentials_storage.get() 90 | self.assertIsInstance(bad_credentials, oauth2client.client.Credentials) 91 | set_store.assert_not_called() 92 | 93 | # Valid credentials stored 94 | session.query(DummyModel).filter_by(key=1).delete() 95 | session.add(DummyModel( 96 | key=1, 97 | credentials=self.credentials, 98 | )) 99 | session.commit() 100 | 101 | self.compare_credentials(credentials_storage.get()) 102 | set_store.assert_called_with(credentials_storage) 103 | 104 | def test_put(self): 105 | session = self.session() 106 | storage = oauth2client.contrib.sqlalchemy.Storage( 107 | session=session, 108 | model_class=DummyModel, 109 | key_name='key', 110 | key_value=1, 111 | property_name='credentials', 112 | ) 113 | # Store invalid credentials first to verify overwriting 114 | storage.put(oauth2client.client.Credentials()) 115 | storage.put(self.credentials) 116 | session.commit() 117 | 118 | entity = session.query(DummyModel).filter_by(key=1).first() 119 | self.compare_credentials(entity.credentials) 120 | 121 | def test_delete(self): 122 | session = self.session() 123 | session.add(DummyModel( 124 | key=1, 125 | credentials=self.credentials, 126 | )) 127 | session.commit() 128 | 129 | query = session.query(DummyModel).filter_by(key=1) 130 | self.assertIsNotNone(query.first()) 131 | oauth2client.contrib.sqlalchemy.Storage( 132 | session=session, 133 | model_class=DummyModel, 134 | key_name='key', 135 | key_value=1, 136 | property_name='credentials', 137 | ).delete() 138 | session.commit() 139 | self.assertIsNone(query.first()) 140 | -------------------------------------------------------------------------------- /tests/data/app.yaml: -------------------------------------------------------------------------------- 1 | # Dummy app.yaml to placate nosegae. 2 | application: oauth2client 3 | version: 1 4 | runtime: python27 5 | api_version: 1 6 | threadsafe: yes 7 | 8 | handlers: 9 | - url: /.* 10 | script: null.app 11 | -------------------------------------------------------------------------------- /tests/data/certs.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "-----BEGIN CERTIFICATE-----\r\nMIIDIzCCAgugAwIBAgIJAMfISuBQ5m+5MA0GCSqGSIb3DQEBBQUAMBUxEzARBgNV\r\nBAMTCnVuaXQtdGVzdHMwHhcNMTExMjA2MTYyNjAyWhcNMjExMjAzMTYyNjAyWjAV\r\nMRMwEQYDVQQDEwp1bml0LXRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\r\nCgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZgkdmM\r\n7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/xmVU1Wer\r\nuQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYsSliS5qQp\r\ngyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18pe+zpyl4\r\n+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xkSBc//fy3\r\nZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABo3YwdDAdBgNVHQ4EFgQU2RQ8yO+O\r\ngN8oVW2SW7RLrfYd9jEwRQYDVR0jBD4wPIAU2RQ8yO+OgN8oVW2SW7RLrfYd9jGh\r\nGaQXMBUxEzARBgNVBAMTCnVuaXQtdGVzdHOCCQDHyErgUOZvuTAMBgNVHRMEBTAD\r\nAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBRv+M/6+FiVu7KXNjFI5pSN17OcW5QUtPr\r\nodJMlWrJBtynn/TA1oJlYu3yV5clc/71Vr/AxuX5xGP+IXL32YDF9lTUJXG/uUGk\r\n+JETpKmQviPbRsvzYhz4pf6ZIOZMc3/GIcNq92ECbseGO+yAgyWUVKMmZM0HqXC9\r\novNslqe0M8C1sLm1zAR5z/h/litE7/8O2ietija3Q/qtl2TOXJdCA6sgjJX2WUql\r\nybrC55ct18NKf3qhpcEkGQvFU40rVYApJpi98DiZPYFdx1oBDp/f4uZ3ojpxRVFT\r\ncDwcJLfNRCPUhormsY7fDS9xSyThiHsW9mjJYdcaKQkwYZ0F11yB\r\n-----END CERTIFICATE-----\r\n" 3 | } 4 | -------------------------------------------------------------------------------- /tests/data/client_secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "web": { 3 | "client_id": "foo_client_id", 4 | "client_secret": "foo_client_secret", 5 | "redirect_uris": [], 6 | "auth_uri": "https://accounts.google.com/o/oauth2/v2/auth", 7 | "token_uri": "https://oauth2.googleapis.com/token", 8 | "revoke_uri": "https://oauth2.googleapis.com/revoke" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/data/gcloud/application_default_credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "private_key_id": "ABCDEF", 3 | "private_key": "Bag Attributes\n friendlyName: key\n localKeyID: 22 7E 04 FC 64 48 20 83 1E C1 BD E3 F5 2F 44 7D EA 99 A5 BC\nKey Attributes: \n-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDh6PSnttDsv+vi\ntUZTP1E3hVBah6PUGDWZhYgNiyW8quTWCmPvBmCR2YzuhUrY5+CtKP8UJOQico+p\noJHSAPsrzSr6YsGs3c9SQOslBmm9Fkh9/f/GZVTVZ6u5AsUmOcVvZ2q7Sz8Vj/aR\naIm0EJqRe9cQ5vvN9sg25rIv4xKwIZJ1VixKWJLmpCmDINqn7xvl+ldlUmSr3aGt\nw21uSDuEJhQlzO3yf2FwJMkJ9SkCm9oVDXyl77OnKXj5bOQ/rojbyGeIxDJSUDWE\nGKyRPuqKi6rSbwg6h2G/Z9qBJkqM5NNTbGRIFz/9/LdmmwvtaqCxlLtD7RVEryAp\n+qTGDk5hAgMBAAECggEBAMYYfNDEYpf4A2SdCLne/9zrrfZ0kphdUkL48MDPj5vN\nTzTRj6f9s5ixZ/+QKn3hdwbguCx13QbH5mocP0IjUhyqoFFHYAWxyyaZfpjM8tO4\nQoEYxby3BpjLe62UXESUzChQSytJZFwIDXKcdIPNO3zvVzufEJcfG5no2b9cIvsG\nDy6J1FNILWxCtDIqBM+G1B1is9DhZnUDgn0iKzINiZmh1I1l7k/4tMnozVIKAfwo\nf1kYjG/d2IzDM02mTeTElz3IKeNriaOIYTZgI26xLJxTkiFnBV4JOWFAZw15X+yR\n+DrjGSIkTfhzbLa20Vt3AFM+LFK0ZoXT2dRnjbYPjQECgYEA+9XJFGwLcEX6pl1p\nIwXAjXKJdju9DDn4lmHTW0Pbw25h1EXONwm/NPafwsWmPll9kW9IwsxUQVUyBC9a\nc3Q7rF1e8ai/qqVFRIZof275MI82ciV2Mw8Hz7FPAUyoju5CvnjAEH4+irt1VE/7\nSgdvQ1gDBQFegS69ijdz+cOhFxkCgYEA5aVoseMy/gIlsCvNPyw9+Jz/zBpKItX0\njGzdF7lhERRO2cursujKaoHntRckHcE3P/Z4K565bvVq+VaVG0T/BcBKPmPHrLmY\niuVXidltW7Jh9/RCVwb5+BvqlwlC470PEwhqoUatY/fPJ74srztrqJHvp1L29FT5\nsdmlJW8YwokCgYAUa3dMgp5C0knKp5RY1KSSU5E11w4zKZgwiWob4lq1dAPWtHpO\nGCo63yyBHImoUJVP75gUw4Cpc4EEudo5tlkIVuHV8nroGVKOhd9/Rb5K47Hke4kk\nBrn5a0Ues9qPDF65Fw1ryPDFSwHufjXAAO5SpZZJF51UGDgiNvDedbBgMQKBgHSk\nt7DjPhtW69234eCckD2fQS5ijBV1p2lMQmCygGM0dXiawvN02puOsCqDPoz+fxm2\nDwPY80cw0M0k9UeMnBxHt25JMDrDan/iTbxu++T/jlNrdebOXFlxlI5y3c7fULDS\nLZcNVzTXwhjlt7yp6d0NgzTyJw2ju9BiREfnTiRBAoGBAOPHrTOnPyjO+bVcCPTB\nWGLsbBd77mVPGIuL0XGrvbVYPE8yIcNbZcthd8VXL/38Ygy8SIZh2ZqsrU1b5WFa\nXUMLnGEODSS8x/GmW3i3KeirW5OxBNjfUzEF4XkJP8m41iTdsQEXQf9DdUY7X+CB\nVL5h7N0VstYhGgycuPpcIUQa\n-----END PRIVATE KEY-----\n", 4 | "client_email": "dummy@google.com", 5 | "client_id": "123", 6 | "type": "service_account" 7 | } 8 | -------------------------------------------------------------------------------- /tests/data/gcloud/application_default_credentials_authorized_user.json: -------------------------------------------------------------------------------- 1 | { 2 | "client_id": "123", 3 | "client_secret": "secret", 4 | "refresh_token": "alabalaportocala", 5 | "type": "authorized_user" 6 | } 7 | -------------------------------------------------------------------------------- /tests/data/gcloud/application_default_credentials_malformed_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "serviceaccount", 3 | "client_id": "123", 4 | "client_secret": "secret", 5 | "refresh_token": "alabalaportocala", 6 | "client_email": "dummy@google.com", 7 | "private_key_id": "ABCDEF", 8 | "private_key": "Bag Attributes\n friendlyName: key\n localKeyID: 22 7E 04 FC 64 48 20 83 1E C1 BD E3 F5 2F 44 7D EA 99 A5 BC\nKey Attributes: \n-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDh6PSnttDsv+vi\ntUZTP1E3hVBah6PUGDWZhYgNiyW8quTWCmPvBmCR2YzuhUrY5+CtKP8UJOQico+p\noJHSAPsrzSr6YsGs3c9SQOslBmm9Fkh9/f/GZVTVZ6u5AsUmOcVvZ2q7Sz8Vj/aR\naIm0EJqRe9cQ5vvN9sg25rIv4xKwIZJ1VixKWJLmpCmDINqn7xvl+ldlUmSr3aGt\nw21uSDuEJhQlzO3yf2FwJMkJ9SkCm9oVDXyl77OnKXj5bOQ/rojbyGeIxDJSUDWE\nGKyRPuqKi6rSbwg6h2G/Z9qBJkqM5NNTbGRIFz/9/LdmmwvtaqCxlLtD7RVEryAp\n+qTGDk5hAgMBAAECggEBAMYYfNDEYpf4A2SdCLne/9zrrfZ0kphdUkL48MDPj5vN\nTzTRj6f9s5ixZ/+QKn3hdwbguCx13QbH5mocP0IjUhyqoFFHYAWxyyaZfpjM8tO4\nQoEYxby3BpjLe62UXESUzChQSytJZFwIDXKcdIPNO3zvVzufEJcfG5no2b9cIvsG\nDy6J1FNILWxCtDIqBM+G1B1is9DhZnUDgn0iKzINiZmh1I1l7k/4tMnozVIKAfwo\nf1kYjG/d2IzDM02mTeTElz3IKeNriaOIYTZgI26xLJxTkiFnBV4JOWFAZw15X+yR\n+DrjGSIkTfhzbLa20Vt3AFM+LFK0ZoXT2dRnjbYPjQECgYEA+9XJFGwLcEX6pl1p\nIwXAjXKJdju9DDn4lmHTW0Pbw25h1EXONwm/NPafwsWmPll9kW9IwsxUQVUyBC9a\nc3Q7rF1e8ai/qqVFRIZof275MI82ciV2Mw8Hz7FPAUyoju5CvnjAEH4+irt1VE/7\nSgdvQ1gDBQFegS69ijdz+cOhFxkCgYEA5aVoseMy/gIlsCvNPyw9+Jz/zBpKItX0\njGzdF7lhERRO2cursujKaoHntRckHcE3P/Z4K565bvVq+VaVG0T/BcBKPmPHrLmY\niuVXidltW7Jh9/RCVwb5+BvqlwlC470PEwhqoUatY/fPJ74srztrqJHvp1L29FT5\nsdmlJW8YwokCgYAUa3dMgp5C0knKp5RY1KSSU5E11w4zKZgwiWob4lq1dAPWtHpO\nGCo63yyBHImoUJVP75gUw4Cpc4EEudo5tlkIVuHV8nroGVKOhd9/Rb5K47Hke4kk\nBrn5a0Ues9qPDF65Fw1ryPDFSwHufjXAAO5SpZZJF51UGDgiNvDedbBgMQKBgHSk\nt7DjPhtW69234eCckD2fQS5ijBV1p2lMQmCygGM0dXiawvN02puOsCqDPoz+fxm2\nDwPY80cw0M0k9UeMnBxHt25JMDrDan/iTbxu++T/jlNrdebOXFlxlI5y3c7fULDS\nLZcNVzTXwhjlt7yp6d0NgzTyJw2ju9BiREfnTiRBAoGBAOPHrTOnPyjO+bVcCPTB\nWGLsbBd77mVPGIuL0XGrvbVYPE8yIcNbZcthd8VXL/38Ygy8SIZh2ZqsrU1b5WFa\nXUMLnGEODSS8x/GmW3i3KeirW5OxBNjfUzEF4XkJP8m41iTdsQEXQf9DdUY7X+CB\nVL5h7N0VstYhGgycuPpcIUQa\n-----END PRIVATE KEY-----\n" 9 | } -------------------------------------------------------------------------------- /tests/data/gcloud/application_default_credentials_malformed_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "service_account", 3 | "client_id": "123", 4 | "client_secret": "secret", 5 | "refresh_token": "alabalaportocala", 6 | "client_email": "dummy@google.com", 7 | "private_key": "Bag Attributes\n friendlyName: key\n localKeyID: 22 7E 04 FC 64 48 20 83 1E C1 BD E3 F5 2F 44 7D EA 99 A5 BC\nKey Attributes: \n-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDh6PSnttDsv+vi\ntUZTP1E3hVBah6PUGDWZhYgNiyW8quTWCmPvBmCR2YzuhUrY5+CtKP8UJOQico+p\noJHSAPsrzSr6YsGs3c9SQOslBmm9Fkh9/f/GZVTVZ6u5AsUmOcVvZ2q7Sz8Vj/aR\naIm0EJqRe9cQ5vvN9sg25rIv4xKwIZJ1VixKWJLmpCmDINqn7xvl+ldlUmSr3aGt\nw21uSDuEJhQlzO3yf2FwJMkJ9SkCm9oVDXyl77OnKXj5bOQ/rojbyGeIxDJSUDWE\nGKyRPuqKi6rSbwg6h2G/Z9qBJkqM5NNTbGRIFz/9/LdmmwvtaqCxlLtD7RVEryAp\n+qTGDk5hAgMBAAECggEBAMYYfNDEYpf4A2SdCLne/9zrrfZ0kphdUkL48MDPj5vN\nTzTRj6f9s5ixZ/+QKn3hdwbguCx13QbH5mocP0IjUhyqoFFHYAWxyyaZfpjM8tO4\nQoEYxby3BpjLe62UXESUzChQSytJZFwIDXKcdIPNO3zvVzufEJcfG5no2b9cIvsG\nDy6J1FNILWxCtDIqBM+G1B1is9DhZnUDgn0iKzINiZmh1I1l7k/4tMnozVIKAfwo\nf1kYjG/d2IzDM02mTeTElz3IKeNriaOIYTZgI26xLJxTkiFnBV4JOWFAZw15X+yR\n+DrjGSIkTfhzbLa20Vt3AFM+LFK0ZoXT2dRnjbYPjQECgYEA+9XJFGwLcEX6pl1p\nIwXAjXKJdju9DDn4lmHTW0Pbw25h1EXONwm/NPafwsWmPll9kW9IwsxUQVUyBC9a\nc3Q7rF1e8ai/qqVFRIZof275MI82ciV2Mw8Hz7FPAUyoju5CvnjAEH4+irt1VE/7\nSgdvQ1gDBQFegS69ijdz+cOhFxkCgYEA5aVoseMy/gIlsCvNPyw9+Jz/zBpKItX0\njGzdF7lhERRO2cursujKaoHntRckHcE3P/Z4K565bvVq+VaVG0T/BcBKPmPHrLmY\niuVXidltW7Jh9/RCVwb5+BvqlwlC470PEwhqoUatY/fPJ74srztrqJHvp1L29FT5\nsdmlJW8YwokCgYAUa3dMgp5C0knKp5RY1KSSU5E11w4zKZgwiWob4lq1dAPWtHpO\nGCo63yyBHImoUJVP75gUw4Cpc4EEudo5tlkIVuHV8nroGVKOhd9/Rb5K47Hke4kk\nBrn5a0Ues9qPDF65Fw1ryPDFSwHufjXAAO5SpZZJF51UGDgiNvDedbBgMQKBgHSk\nt7DjPhtW69234eCckD2fQS5ijBV1p2lMQmCygGM0dXiawvN02puOsCqDPoz+fxm2\nDwPY80cw0M0k9UeMnBxHt25JMDrDan/iTbxu++T/jlNrdebOXFlxlI5y3c7fULDS\nLZcNVzTXwhjlt7yp6d0NgzTyJw2ju9BiREfnTiRBAoGBAOPHrTOnPyjO+bVcCPTB\nWGLsbBd77mVPGIuL0XGrvbVYPE8yIcNbZcthd8VXL/38Ygy8SIZh2ZqsrU1b5WFa\nXUMLnGEODSS8x/GmW3i3KeirW5OxBNjfUzEF4XkJP8m41iTdsQEXQf9DdUY7X+CB\nVL5h7N0VstYhGgycuPpcIUQa\n-----END PRIVATE KEY-----\n" 8 | } -------------------------------------------------------------------------------- /tests/data/gcloud/application_default_credentials_malformed_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "service_account" 3 | "client_id": "123", 4 | "client_secret": "secret", 5 | "refresh_token": "alabalaportocala", 6 | "client_email": "dummy@google.com", 7 | "private_key_id": "ABCDEF", 8 | "private_key": "Bag Attributes\n friendlyName: key\n localKeyID: 22 7E 04 FC 64 48 20 83 1E C1 BD E3 F5 2F 44 7D EA 99 A5 BC\nKey Attributes: \n-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDh6PSnttDsv+vi\ntUZTP1E3hVBah6PUGDWZhYgNiyW8quTWCmPvBmCR2YzuhUrY5+CtKP8UJOQico+p\noJHSAPsrzSr6YsGs3c9SQOslBmm9Fkh9/f/GZVTVZ6u5AsUmOcVvZ2q7Sz8Vj/aR\naIm0EJqRe9cQ5vvN9sg25rIv4xKwIZJ1VixKWJLmpCmDINqn7xvl+ldlUmSr3aGt\nw21uSDuEJhQlzO3yf2FwJMkJ9SkCm9oVDXyl77OnKXj5bOQ/rojbyGeIxDJSUDWE\nGKyRPuqKi6rSbwg6h2G/Z9qBJkqM5NNTbGRIFz/9/LdmmwvtaqCxlLtD7RVEryAp\n+qTGDk5hAgMBAAECggEBAMYYfNDEYpf4A2SdCLne/9zrrfZ0kphdUkL48MDPj5vN\nTzTRj6f9s5ixZ/+QKn3hdwbguCx13QbH5mocP0IjUhyqoFFHYAWxyyaZfpjM8tO4\nQoEYxby3BpjLe62UXESUzChQSytJZFwIDXKcdIPNO3zvVzufEJcfG5no2b9cIvsG\nDy6J1FNILWxCtDIqBM+G1B1is9DhZnUDgn0iKzINiZmh1I1l7k/4tMnozVIKAfwo\nf1kYjG/d2IzDM02mTeTElz3IKeNriaOIYTZgI26xLJxTkiFnBV4JOWFAZw15X+yR\n+DrjGSIkTfhzbLa20Vt3AFM+LFK0ZoXT2dRnjbYPjQECgYEA+9XJFGwLcEX6pl1p\nIwXAjXKJdju9DDn4lmHTW0Pbw25h1EXONwm/NPafwsWmPll9kW9IwsxUQVUyBC9a\nc3Q7rF1e8ai/qqVFRIZof275MI82ciV2Mw8Hz7FPAUyoju5CvnjAEH4+irt1VE/7\nSgdvQ1gDBQFegS69ijdz+cOhFxkCgYEA5aVoseMy/gIlsCvNPyw9+Jz/zBpKItX0\njGzdF7lhERRO2cursujKaoHntRckHcE3P/Z4K565bvVq+VaVG0T/BcBKPmPHrLmY\niuVXidltW7Jh9/RCVwb5+BvqlwlC470PEwhqoUatY/fPJ74srztrqJHvp1L29FT5\nsdmlJW8YwokCgYAUa3dMgp5C0knKp5RY1KSSU5E11w4zKZgwiWob4lq1dAPWtHpO\nGCo63yyBHImoUJVP75gUw4Cpc4EEudo5tlkIVuHV8nroGVKOhd9/Rb5K47Hke4kk\nBrn5a0Ues9qPDF65Fw1ryPDFSwHufjXAAO5SpZZJF51UGDgiNvDedbBgMQKBgHSk\nt7DjPhtW69234eCckD2fQS5ijBV1p2lMQmCygGM0dXiawvN02puOsCqDPoz+fxm2\nDwPY80cw0M0k9UeMnBxHt25JMDrDan/iTbxu++T/jlNrdebOXFlxlI5y3c7fULDS\nLZcNVzTXwhjlt7yp6d0NgzTyJw2ju9BiREfnTiRBAoGBAOPHrTOnPyjO+bVcCPTB\nWGLsbBd77mVPGIuL0XGrvbVYPE8yIcNbZcthd8VXL/38Ygy8SIZh2ZqsrU1b5WFa\nXUMLnGEODSS8x/GmW3i3KeirW5OxBNjfUzEF4XkJP8m41iTdsQEXQf9DdUY7X+CB\nVL5h7N0VstYhGgycuPpcIUQa\n-----END PRIVATE KEY-----\n" 9 | } -------------------------------------------------------------------------------- /tests/data/key.json.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/oauth2client/50d20532a748f18e53f7d24ccbe6647132c979a9/tests/data/key.json.enc -------------------------------------------------------------------------------- /tests/data/key.p12.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/oauth2client/50d20532a748f18e53f7d24ccbe6647132c979a9/tests/data/key.p12.enc -------------------------------------------------------------------------------- /tests/data/pem_from_pkcs12.pem: -------------------------------------------------------------------------------- 1 | Bag Attributes 2 | friendlyName: key 3 | localKeyID: 22 7E 04 FC 64 48 20 83 1E C1 BD E3 F5 2F 44 7D EA 99 A5 BC 4 | Key Attributes: 5 | -----BEGIN PRIVATE KEY----- 6 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDh6PSnttDsv+vi 7 | tUZTP1E3hVBah6PUGDWZhYgNiyW8quTWCmPvBmCR2YzuhUrY5+CtKP8UJOQico+p 8 | oJHSAPsrzSr6YsGs3c9SQOslBmm9Fkh9/f/GZVTVZ6u5AsUmOcVvZ2q7Sz8Vj/aR 9 | aIm0EJqRe9cQ5vvN9sg25rIv4xKwIZJ1VixKWJLmpCmDINqn7xvl+ldlUmSr3aGt 10 | w21uSDuEJhQlzO3yf2FwJMkJ9SkCm9oVDXyl77OnKXj5bOQ/rojbyGeIxDJSUDWE 11 | GKyRPuqKi6rSbwg6h2G/Z9qBJkqM5NNTbGRIFz/9/LdmmwvtaqCxlLtD7RVEryAp 12 | +qTGDk5hAgMBAAECggEBAMYYfNDEYpf4A2SdCLne/9zrrfZ0kphdUkL48MDPj5vN 13 | TzTRj6f9s5ixZ/+QKn3hdwbguCx13QbH5mocP0IjUhyqoFFHYAWxyyaZfpjM8tO4 14 | QoEYxby3BpjLe62UXESUzChQSytJZFwIDXKcdIPNO3zvVzufEJcfG5no2b9cIvsG 15 | Dy6J1FNILWxCtDIqBM+G1B1is9DhZnUDgn0iKzINiZmh1I1l7k/4tMnozVIKAfwo 16 | f1kYjG/d2IzDM02mTeTElz3IKeNriaOIYTZgI26xLJxTkiFnBV4JOWFAZw15X+yR 17 | +DrjGSIkTfhzbLa20Vt3AFM+LFK0ZoXT2dRnjbYPjQECgYEA+9XJFGwLcEX6pl1p 18 | IwXAjXKJdju9DDn4lmHTW0Pbw25h1EXONwm/NPafwsWmPll9kW9IwsxUQVUyBC9a 19 | c3Q7rF1e8ai/qqVFRIZof275MI82ciV2Mw8Hz7FPAUyoju5CvnjAEH4+irt1VE/7 20 | SgdvQ1gDBQFegS69ijdz+cOhFxkCgYEA5aVoseMy/gIlsCvNPyw9+Jz/zBpKItX0 21 | jGzdF7lhERRO2cursujKaoHntRckHcE3P/Z4K565bvVq+VaVG0T/BcBKPmPHrLmY 22 | iuVXidltW7Jh9/RCVwb5+BvqlwlC470PEwhqoUatY/fPJ74srztrqJHvp1L29FT5 23 | sdmlJW8YwokCgYAUa3dMgp5C0knKp5RY1KSSU5E11w4zKZgwiWob4lq1dAPWtHpO 24 | GCo63yyBHImoUJVP75gUw4Cpc4EEudo5tlkIVuHV8nroGVKOhd9/Rb5K47Hke4kk 25 | Brn5a0Ues9qPDF65Fw1ryPDFSwHufjXAAO5SpZZJF51UGDgiNvDedbBgMQKBgHSk 26 | t7DjPhtW69234eCckD2fQS5ijBV1p2lMQmCygGM0dXiawvN02puOsCqDPoz+fxm2 27 | DwPY80cw0M0k9UeMnBxHt25JMDrDan/iTbxu++T/jlNrdebOXFlxlI5y3c7fULDS 28 | LZcNVzTXwhjlt7yp6d0NgzTyJw2ju9BiREfnTiRBAoGBAOPHrTOnPyjO+bVcCPTB 29 | WGLsbBd77mVPGIuL0XGrvbVYPE8yIcNbZcthd8VXL/38Ygy8SIZh2ZqsrU1b5WFa 30 | XUMLnGEODSS8x/GmW3i3KeirW5OxBNjfUzEF4XkJP8m41iTdsQEXQf9DdUY7X+CB 31 | VL5h7N0VstYhGgycuPpcIUQa 32 | -----END PRIVATE KEY----- 33 | -------------------------------------------------------------------------------- /tests/data/pem_from_pkcs12_alternate.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj 3 | 7wZgkdmM7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/ 4 | xmVU1WeruQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYs 5 | SliS5qQpgyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18 6 | pe+zpyl4+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xk 7 | SBc//fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABAoIBAQDGGHzQxGKX+ANk 8 | nQi53v/c6632dJKYXVJC+PDAz4+bzU800Y+n/bOYsWf/kCp94XcG4Lgsdd0Gx+Zq 9 | HD9CI1IcqqBRR2AFscsmmX6YzPLTuEKBGMW8twaYy3utlFxElMwoUEsrSWRcCA1y 10 | nHSDzTt871c7nxCXHxuZ6Nm/XCL7Bg8uidRTSC1sQrQyKgTPhtQdYrPQ4WZ1A4J9 11 | IisyDYmZodSNZe5P+LTJ6M1SCgH8KH9ZGIxv3diMwzNNpk3kxJc9yCnja4mjiGE2 12 | YCNusSycU5IhZwVeCTlhQGcNeV/skfg64xkiJE34c2y2ttFbdwBTPixStGaF09nU 13 | Z422D40BAoGBAPvVyRRsC3BF+qZdaSMFwI1yiXY7vQw5+JZh01tD28NuYdRFzjcJ 14 | vzT2n8LFpj5ZfZFvSMLMVEFVMgQvWnN0O6xdXvGov6qlRUSGaH9u+TCPNnIldjMP 15 | B8+xTwFMqI7uQr54wBB+Poq7dVRP+0oHb0NYAwUBXoEuvYo3c/nDoRcZAoGBAOWl 16 | aLHjMv4CJbArzT8sPfic/8waSiLV9Ixs3Re5YREUTtnLq7LoymqB57UXJB3BNz/2 17 | eCueuW71avlWlRtE/wXASj5jx6y5mIrlV4nZbVuyYff0QlcG+fgb6pcJQuO9DxMI 18 | aqFGrWP3zye+LK87a6iR76dS9vRU+bHZpSVvGMKJAoGAFGt3TIKeQtJJyqeUWNSk 19 | klORNdcOMymYMIlqG+JatXQD1rR6ThgqOt8sgRyJqFCVT++YFMOAqXOBBLnaObZZ 20 | CFbh1fJ66BlSjoXff0W+SuOx5HuJJAa5+WtFHrPajwxeuRcNa8jwxUsB7n41wADu 21 | UqWWSRedVBg4Ijbw3nWwYDECgYB0pLew4z4bVuvdt+HgnJA9n0EuYowVdadpTEJg 22 | soBjNHV4msLzdNqbjrAqgz6M/n8Ztg8D2PNHMNDNJPVHjJwcR7duSTA6w2p/4k28 23 | bvvk/45Ta3XmzlxZcZSOct3O31Cw0i2XDVc018IY5be8qendDYM08icNo7vQYkRH 24 | 504kQQKBgQDjx60zpz8ozvm1XAj0wVhi7GwXe+5lTxiLi9Fxq721WDxPMiHDW2XL 25 | YXfFVy/9/GIMvEiGYdmarK1NW+VhWl1DC5xhDg0kvMfxplt4tynoq1uTsQTY31Mx 26 | BeF5CT/JuNYk3bEBF0H/Q3VGO1/ggVS+YezdFbLWIRoMnLj6XCFEGg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/data/privatekey.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/oauth2client/50d20532a748f18e53f7d24ccbe6647132c979a9/tests/data/privatekey.p12 -------------------------------------------------------------------------------- /tests/data/privatekey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj 3 | 7wZgkdmM7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/ 4 | xmVU1WeruQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYs 5 | SliS5qQpgyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18 6 | pe+zpyl4+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xk 7 | SBc//fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABAoIBAQDGGHzQxGKX+ANk 8 | nQi53v/c6632dJKYXVJC+PDAz4+bzU800Y+n/bOYsWf/kCp94XcG4Lgsdd0Gx+Zq 9 | HD9CI1IcqqBRR2AFscsmmX6YzPLTuEKBGMW8twaYy3utlFxElMwoUEsrSWRcCA1y 10 | nHSDzTt871c7nxCXHxuZ6Nm/XCL7Bg8uidRTSC1sQrQyKgTPhtQdYrPQ4WZ1A4J9 11 | IisyDYmZodSNZe5P+LTJ6M1SCgH8KH9ZGIxv3diMwzNNpk3kxJc9yCnja4mjiGE2 12 | YCNusSycU5IhZwVeCTlhQGcNeV/skfg64xkiJE34c2y2ttFbdwBTPixStGaF09nU 13 | Z422D40BAoGBAPvVyRRsC3BF+qZdaSMFwI1yiXY7vQw5+JZh01tD28NuYdRFzjcJ 14 | vzT2n8LFpj5ZfZFvSMLMVEFVMgQvWnN0O6xdXvGov6qlRUSGaH9u+TCPNnIldjMP 15 | B8+xTwFMqI7uQr54wBB+Poq7dVRP+0oHb0NYAwUBXoEuvYo3c/nDoRcZAoGBAOWl 16 | aLHjMv4CJbArzT8sPfic/8waSiLV9Ixs3Re5YREUTtnLq7LoymqB57UXJB3BNz/2 17 | eCueuW71avlWlRtE/wXASj5jx6y5mIrlV4nZbVuyYff0QlcG+fgb6pcJQuO9DxMI 18 | aqFGrWP3zye+LK87a6iR76dS9vRU+bHZpSVvGMKJAoGAFGt3TIKeQtJJyqeUWNSk 19 | klORNdcOMymYMIlqG+JatXQD1rR6ThgqOt8sgRyJqFCVT++YFMOAqXOBBLnaObZZ 20 | CFbh1fJ66BlSjoXff0W+SuOx5HuJJAa5+WtFHrPajwxeuRcNa8jwxUsB7n41wADu 21 | UqWWSRedVBg4Ijbw3nWwYDECgYB0pLew4z4bVuvdt+HgnJA9n0EuYowVdadpTEJg 22 | soBjNHV4msLzdNqbjrAqgz6M/n8Ztg8D2PNHMNDNJPVHjJwcR7duSTA6w2p/4k28 23 | bvvk/45Ta3XmzlxZcZSOct3O31Cw0i2XDVc018IY5be8qendDYM08icNo7vQYkRH 24 | 504kQQKBgQDjx60zpz8ozvm1XAj0wVhi7GwXe+5lTxiLi9Fxq721WDxPMiHDW2XL 25 | YXfFVy/9/GIMvEiGYdmarK1NW+VhWl1DC5xhDg0kvMfxplt4tynoq1uTsQTY31Mx 26 | BeF5CT/JuNYk3bEBF0H/Q3VGO1/ggVS+YezdFbLWIRoMnLj6XCFEGg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/data/privatekey.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIIBCgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZg 3 | kdmM7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/xmVU 4 | 1WeruQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYsSliS 5 | 5qQpgyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18pe+z 6 | pyl4+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xkSBc/ 7 | /fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQAB 8 | -----END RSA PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /tests/data/public_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDIzCCAgugAwIBAgIJAMfISuBQ5m+5MA0GCSqGSIb3DQEBBQUAMBUxEzARBgNV 3 | BAMTCnVuaXQtdGVzdHMwHhcNMTExMjA2MTYyNjAyWhcNMjExMjAzMTYyNjAyWjAV 4 | MRMwEQYDVQQDEwp1bml0LXRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 5 | CgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZgkdmM 6 | 7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/xmVU1Wer 7 | uQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYsSliS5qQp 8 | gyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18pe+zpyl4 9 | +WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xkSBc//fy3 10 | ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABo3YwdDAdBgNVHQ4EFgQU2RQ8yO+O 11 | gN8oVW2SW7RLrfYd9jEwRQYDVR0jBD4wPIAU2RQ8yO+OgN8oVW2SW7RLrfYd9jGh 12 | GaQXMBUxEzARBgNVBAMTCnVuaXQtdGVzdHOCCQDHyErgUOZvuTAMBgNVHRMEBTAD 13 | AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBRv+M/6+FiVu7KXNjFI5pSN17OcW5QUtPr 14 | odJMlWrJBtynn/TA1oJlYu3yV5clc/71Vr/AxuX5xGP+IXL32YDF9lTUJXG/uUGk 15 | +JETpKmQviPbRsvzYhz4pf6ZIOZMc3/GIcNq92ECbseGO+yAgyWUVKMmZM0HqXC9 16 | ovNslqe0M8C1sLm1zAR5z/h/litE7/8O2ietija3Q/qtl2TOXJdCA6sgjJX2WUql 17 | ybrC55ct18NKf3qhpcEkGQvFU40rVYApJpi98DiZPYFdx1oBDp/f4uZ3ojpxRVFT 18 | cDwcJLfNRCPUhormsY7fDS9xSyThiHsW9mjJYdcaKQkwYZ0F11yB 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /tests/data/publickey_openssl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4ej0p7bQ7L/r4rVGUz9R 3 | N4VQWoej1Bg1mYWIDYslvKrk1gpj7wZgkdmM7oVK2OfgrSj/FCTkInKPqaCR0gD7 4 | K80q+mLBrN3PUkDrJQZpvRZIff3/xmVU1WeruQLFJjnFb2dqu0s/FY/2kWiJtBCa 5 | kXvXEOb7zfbINuayL+MSsCGSdVYsSliS5qQpgyDap+8b5fpXZVJkq92hrcNtbkg7 6 | hCYUJczt8n9hcCTJCfUpApvaFQ18pe+zpyl4+WzkP66I28hniMQyUlA1hBiskT7q 7 | iouq0m8IOodhv2fagSZKjOTTU2xkSBc//fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5O 8 | YQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /tests/data/unfilled_client_secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "web": { 3 | "client_id": "[[INSERT CLIENT ID HERE]]", 4 | "client_secret": "[[INSERT CLIENT SECRET HERE]]", 5 | "redirect_uris": [], 6 | "auth_uri": "https://accounts.google.com/o/oauth2/v2/auth", 7 | "token_uri": "https://oauth2.googleapis.com/token" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/data/user-key.json.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/oauth2client/50d20532a748f18e53f7d24ccbe6647132c979a9/tests/data/user-key.json.enc -------------------------------------------------------------------------------- /tests/http_mock.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """HTTP helpers mock functionality.""" 16 | 17 | 18 | from six.moves import http_client 19 | 20 | 21 | class ResponseMock(dict): 22 | """Mock HTTP response""" 23 | 24 | def __init__(self, vals=None): 25 | if vals is None: 26 | vals = {} 27 | self.update(vals) 28 | self.status = int(self.get('status', http_client.OK)) 29 | 30 | 31 | class HttpMock(object): 32 | """Mock of HTTP object.""" 33 | 34 | def __init__(self, headers=None, data=None): 35 | """HttpMock constructor. 36 | 37 | Args: 38 | headers: dict, header to return with response 39 | """ 40 | if headers is None: 41 | headers = {'status': http_client.OK} 42 | self.data = data 43 | self.response_headers = headers 44 | self.headers = None 45 | self.uri = None 46 | self.method = None 47 | self.body = None 48 | self.headers = None 49 | self.requests = 0 50 | 51 | def request(self, uri, 52 | method='GET', 53 | body=None, 54 | headers=None, 55 | redirections=1, 56 | connection_type=None): 57 | self.uri = uri 58 | self.method = method 59 | self.body = body 60 | self.headers = headers 61 | self.redirections = redirections 62 | self.requests += 1 63 | return ResponseMock(self.response_headers), self.data 64 | 65 | 66 | class HttpMockSequence(object): 67 | """Mock of HTTP object with multiple return values. 68 | 69 | Mocks a sequence of calls to request returning different responses for each 70 | call. Create an instance initialized with the desired response headers 71 | and content and then use as if an HttpMock instance:: 72 | 73 | http = HttpMockSequence([ 74 | ({'status': '401'}, b''), 75 | ({'status': '200'}, b'{"access_token":"1/3w","expires_in":3600}'), 76 | ({'status': '200'}, 'echo_request_headers'), 77 | ]) 78 | resp, content = http.request('http://examples.com') 79 | 80 | There are special values you can pass in for content to trigger 81 | behavours that are helpful in testing. 82 | 83 | * 'echo_request_headers' means return the request headers in the response 84 | body 85 | * 'echo_request_body' means return the request body in the response body 86 | """ 87 | 88 | def __init__(self, iterable): 89 | """HttpMockSequence constructor. 90 | 91 | Args: 92 | iterable: iterable, a sequence of pairs of (headers, body) 93 | """ 94 | self._iterable = iterable 95 | self.requests = [] 96 | 97 | def request(self, uri, 98 | method='GET', 99 | body=None, 100 | headers=None, 101 | redirections=1, 102 | connection_type=None): 103 | resp, content = self._iterable.pop(0) 104 | self.requests.append({ 105 | 'method': method, 106 | 'uri': uri, 107 | 'body': body, 108 | 'headers': headers, 109 | }) 110 | # Read any underlying stream before sending the request. 111 | body_stream_content = (body.read() 112 | if getattr(body, 'read', None) else None) 113 | if content == 'echo_request_headers': 114 | content = headers 115 | elif content == 'echo_request_body': 116 | content = (body 117 | if body_stream_content is None else body_stream_content) 118 | return ResponseMock(resp), content 119 | 120 | 121 | class CacheMock(object): 122 | 123 | def __init__(self): 124 | self.cache = {} 125 | 126 | def get(self, key, namespace=''): 127 | # ignoring namespace for easier testing 128 | return self.cache.get(key, None) 129 | -------------------------------------------------------------------------------- /tests/test__pkce.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import unittest 16 | 17 | import mock 18 | 19 | from oauth2client import _pkce 20 | 21 | 22 | class PKCETests(unittest.TestCase): 23 | 24 | @mock.patch('oauth2client._pkce.os.urandom') 25 | def test_verifier(self, fake_urandom): 26 | canned_randomness = ( 27 | b'\x98\x10D7\xf3\xb7\xaa\xfc\xdd\xd3M\xe2' 28 | b'\xa3,\x06\xa0\xb0\xa9\xb4\x8f\xcb\xd0' 29 | b'\xf5\x86N2p\x8c]!W\x9a\xed54\x99\x9d' 30 | b'\x8dv\\\xa7/\x81\xf3J\x98\xc3\x90\xee' 31 | b'\xb0\x8c\xb7Zc#\x05M0O\x08\xda\t\x1f\x07' 32 | ) 33 | fake_urandom.return_value = canned_randomness 34 | expected = ( 35 | b'mBBEN_O3qvzd003ioywGoLCptI_L0PWGTjJwjF0hV5rt' 36 | b'NTSZnY12XKcvgfNKmMOQ7rCMt1pjIwVNME8I2gkfBw' 37 | ) 38 | result = _pkce.code_verifier() 39 | self.assertEqual(result, expected) 40 | 41 | def test_verifier_too_long(self): 42 | with self.assertRaises(ValueError) as caught: 43 | _pkce.code_verifier(97) 44 | self.assertIn("too long", str(caught.exception)) 45 | 46 | def test_verifier_too_short(self): 47 | with self.assertRaises(ValueError) as caught: 48 | _pkce.code_verifier(30) 49 | self.assertIn("too short", str(caught.exception)) 50 | 51 | def test_challenge(self): 52 | result = _pkce.code_challenge(b'SOME_VERIFIER') 53 | expected = b'6xJCQsjTtS3zjUwd8_ZqH0SyviGHnp5PsHXWKOCqDuI' 54 | self.assertEqual(result, expected) 55 | -------------------------------------------------------------------------------- /tests/test__pycrypto_crypt.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Unit tests for oauth2client._pycrypto_crypt.""" 15 | 16 | import os 17 | import unittest 18 | 19 | from oauth2client import crypt 20 | 21 | 22 | class TestPyCryptoVerifier(unittest.TestCase): 23 | 24 | PUBLIC_CERT_FILENAME = os.path.join(os.path.dirname(__file__), 25 | 'data', 'public_cert.pem') 26 | PRIVATE_KEY_FILENAME = os.path.join(os.path.dirname(__file__), 27 | 'data', 'privatekey.pem') 28 | 29 | def _load_public_cert_bytes(self): 30 | with open(self.PUBLIC_CERT_FILENAME, 'rb') as fh: 31 | return fh.read() 32 | 33 | def _load_private_key_bytes(self): 34 | with open(self.PRIVATE_KEY_FILENAME, 'rb') as fh: 35 | return fh.read() 36 | 37 | def test_verify_success(self): 38 | to_sign = b'foo' 39 | signer = crypt.PyCryptoSigner.from_string( 40 | self._load_private_key_bytes()) 41 | actual_signature = signer.sign(to_sign) 42 | 43 | verifier = crypt.PyCryptoVerifier.from_string( 44 | self._load_public_cert_bytes(), is_x509_cert=True) 45 | self.assertTrue(verifier.verify(to_sign, actual_signature)) 46 | 47 | def test_verify_failure(self): 48 | verifier = crypt.PyCryptoVerifier.from_string( 49 | self._load_public_cert_bytes(), is_x509_cert=True) 50 | bad_signature = b'' 51 | self.assertFalse(verifier.verify(b'foo', bad_signature)) 52 | 53 | def test_verify_bad_key(self): 54 | verifier = crypt.PyCryptoVerifier.from_string( 55 | self._load_public_cert_bytes(), is_x509_cert=True) 56 | bad_signature = b'' 57 | self.assertFalse(verifier.verify(b'foo', bad_signature)) 58 | 59 | def test_from_string_unicode_key(self): 60 | public_key = self._load_public_cert_bytes() 61 | public_key = public_key.decode('utf-8') 62 | verifier = crypt.PyCryptoVerifier.from_string( 63 | public_key, is_x509_cert=True) 64 | self.assertIsInstance(verifier, crypt.PyCryptoVerifier) 65 | 66 | 67 | class TestPyCryptoSigner(unittest.TestCase): 68 | 69 | def test_from_string_bad_key(self): 70 | key_bytes = 'definitely-not-pem-format' 71 | with self.assertRaises(NotImplementedError): 72 | crypt.PyCryptoSigner.from_string(key_bytes) 73 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = flake8,py27,py34,py35,gae,cover 3 | 4 | [testenv] 5 | basedeps = mock>=1.3.0 6 | pycrypto>=2.6 7 | cryptography>=1.0 8 | pyopenssl>=0.14 9 | webtest 10 | pytest 11 | flask 12 | sqlalchemy 13 | fasteners 14 | deps = {[testenv]basedeps} 15 | django 16 | keyring 17 | jsonpickle 18 | setenv = 19 | pypy: with_gmp=no 20 | DJANGO_SETTINGS_MODULE=tests.contrib.django_util.settings 21 | commands = 22 | py.test {posargs} 23 | 24 | [coverbase] 25 | basepython = python2.7 26 | commands = 27 | py.test \ 28 | --cov=oauth2client \ 29 | --cov=tests 30 | py.test \ 31 | --cov=oauth2client \ 32 | --cov=tests \ 33 | --cov-append \ 34 | --gae-sdk={env:GAE_PYTHONPATH:} \ 35 | tests/contrib/appengine 36 | deps = {[testenv]deps} 37 | coverage 38 | pytest-cov 39 | 40 | [testenv:cover] 41 | basepython = {[coverbase]basepython} 42 | commands = 43 | {[coverbase]commands} 44 | coverage report --show-missing --fail-under=100 45 | deps = 46 | {[coverbase]deps} 47 | 48 | [testenv:docs] 49 | basepython = python2.7 50 | deps = 51 | {[testenv:cover]deps} 52 | python-gflags 53 | pyyaml 54 | sphinx>=1.3b2 55 | sphinx-rtd-theme 56 | webapp2 57 | commands = {toxinidir}/scripts/build_docs.sh 58 | 59 | [testenv:gae] 60 | basepython = python2.7 61 | deps = {[testenv]basedeps} 62 | commands = 63 | py.test --gae-sdk={env:GAE_PYTHONPATH:} tests/contrib/appengine 64 | 65 | [testenv:system-tests] 66 | basepython = 67 | python2.7 68 | commands = 69 | {toxinidir}/scripts/run_system_tests.sh 70 | deps = 71 | pycrypto>=2.6 72 | cryptography>=1.0 73 | pyopenssl>=0.14 74 | passenv = GOOGLE_* OAUTH2CLIENT_* TRAVIS* encrypted_* 75 | 76 | [testenv:system-tests3] 77 | basepython = 78 | python3.4 79 | commands = 80 | {toxinidir}/scripts/run_system_tests.sh 81 | deps = 82 | pycrypto>=2.6 83 | cryptography>=1.0 84 | pyopenssl>=0.14 85 | passenv = {[testenv:system-tests]passenv} 86 | 87 | [testenv:gce-system-tests] 88 | basepython = 89 | python2.7 90 | commands = 91 | python {toxinidir}/scripts/run_gce_system_tests.py 92 | deps = 93 | pycrypto>=2.6 94 | passenv = {[testenv:system-tests]passenv} 95 | 96 | [testenv:flake8] 97 | commands = flake8 --import-order-style google {posargs} 98 | deps = 99 | flake8-putty 100 | flake8-import-order 101 | 102 | [flake8] 103 | exclude = .tox,.git,./*.egg,build,.cache,env,__pycache__ 104 | application-import-names = oauth2client, tests 105 | putty-ignore = 106 | # E402 module level import not at top of file 107 | # This file has needed configurations defined before import 108 | docs/conf.py : E402 109 | # E501 line too long 110 | # Ignore lines over 80 chars that include "http:" or "https:" 111 | /http:/ : E501 112 | /https:/ : E501 113 | # E722 do not use bare except 114 | # Existing sloppy usages. 115 | oauth2client/crypt.py : E722 116 | oauth2client/contrib/multiprocess_file_storage.py : E722 117 | --------------------------------------------------------------------------------